View Javadoc
1   /*
2    * Copyright (C) 2003-2014 eXo Platform SAS.
3    *
4    * This is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU Lesser General Public License as
6    * published by the Free Software Foundation; either version 3 of
7    * the License, or (at your option) any later version.
8    *
9    * This software is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this software; if not, write to the Free
16   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18   */
19  package org.exoplatform.utils;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URLConnection;
29  import java.text.DecimalFormat;
30  import java.text.Normalizer;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import javax.xml.parsers.DocumentBuilder;
39  import javax.xml.parsers.DocumentBuilderFactory;
40  import javax.xml.parsers.ParserConfigurationException;
41  
42  import org.apache.http.HttpResponse;
43  import org.apache.http.HttpStatus;
44  import org.apache.http.client.methods.HttpPut;
45  import org.apache.http.entity.FileEntity;
46  import org.w3c.dom.Document;
47  import org.w3c.dom.Element;
48  import org.w3c.dom.Node;
49  import org.w3c.dom.NodeList;
50  import org.xml.sax.SAXException;
51  
52  import org.exoplatform.R;
53  import org.exoplatform.model.ExoFile;
54  import org.exoplatform.singleton.AccountSetting;
55  import org.exoplatform.singleton.DocumentHelper;
56  import org.exoplatform.ui.WebViewActivity;
57  import org.exoplatform.utils.CompatibleFileOpen.FileOpenRequest;
58  import org.exoplatform.utils.CompatibleFileOpen.FileOpenRequestResult;
59  import org.exoplatform.widget.UnreadableFileDialog;
60  
61  import android.Manifest;
62  import android.annotation.TargetApi;
63  import android.app.Activity;
64  import android.content.ComponentName;
65  import android.content.ContentResolver;
66  import android.content.ContentUris;
67  import android.content.Context;
68  import android.content.Intent;
69  import android.content.SharedPreferences;
70  import android.content.pm.PackageManager;
71  import android.database.Cursor;
72  import android.graphics.Bitmap;
73  import android.graphics.Matrix;
74  import android.media.ExifInterface;
75  import android.net.Uri;
76  import android.os.Build;
77  import android.os.Bundle;
78  import android.os.Environment;
79  import android.os.StatFs;
80  import android.provider.MediaStore;
81  import android.provider.OpenableColumns;
82  import android.support.v4.app.ActivityCompat;
83  import android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback;
84  import android.support.v4.content.ContextCompat;
85  import android.text.Html;
86  import android.webkit.MimeTypeMap;
87  
88  public class ExoDocumentUtils {
89  
90    private static final String  LOG_TAG              = "____eXo____ExoDocumentUtils____";
91  
92    public static final String   ALL_VIDEO_TYPE       = "video/*";
93  
94    public static final String   ALL_AUDIO_TYPE       = "audio/*";
95  
96    public static final String   ALL_IMAGE_TYPE       = "image/*";
97  
98    public static final String   ALL_TEXT_TYPE        = "text/*";
99  
100   public static final String   IMAGE_TYPE           = "image";
101 
102   public static final String   TEXT_TYPE            = "text";
103 
104   public static final String   VIDEO_TYPE           = "video";
105 
106   public static final String   AUDIO_TYPE           = "audio";
107 
108   public static final String   MSWORD_TYPE          = "application/msword";
109 
110   public static final String   OPEN_WORD_TYPE       = "application/vnd.oasis.opendocument.text";
111 
112   public static final String   PDF_TYPE             = "application/pdf";
113 
114   public static final String   XLS_TYPE             = "application/xls";
115 
116   public static final String   OPEN_XLS_TYPE        = "application/vnd.oasis.opendocument.spreadsheet";
117 
118   public static final String   POWERPOINT_TYPE      = "application/vnd.ms-powerpoint";
119 
120   public static final String   OPEN_POWERPOINT_TYPE = "application/vnd.oasis.opendocument.presentation";
121 
122   public static final String[] FORBIDDEN_TYPES      = new String[] { "application/octet-stream" };
123   
124   public static boolean isEnoughMemory(int fileSize) {
125     if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
126       int freeSpace = getFreeMemory(Environment.getExternalStorageDirectory().getAbsolutePath());
127       if (freeSpace > fileSize) {
128         return true;
129       } else
130         return false;
131 
132     } else
133       return false;
134   }
135 
136   /*
137    * Get free memory from path
138    */
139   public static int getFreeMemory(String path) {
140     StatFs statFs = new StatFs(path);
141     int free = (statFs.getAvailableBlocks() * statFs.getBlockSize());
142     return Math.abs(free);
143   }
144 
145   /*
146    * Display file size by string decimal format
147    */
148   public static String getFileSize(long fileSize) {
149     int freeUnit;
150     for (freeUnit = 0; fileSize >= 100; freeUnit++) {
151       fileSize /= 1024;
152     }
153     DecimalFormat decFormat = new DecimalFormat("0.0");
154     String doubleString = decFormat.format(fileSize);
155     StringBuffer buffer = new StringBuffer();
156     buffer.append(doubleString);
157     switch (freeUnit) {
158     case 0:
159       buffer.append("B");
160       break;
161     case 1:
162       buffer.append("KB");
163       break;
164     case 2:
165       buffer.append("MB");
166       break;
167     case 3:
168       buffer.append("GB");
169       break;
170     case 4:
171       buffer.append("TB");
172       break;
173     default:
174       buffer.append("err");
175       break;
176     }
177     return buffer.toString();
178   }
179 
180   /*
181    * If content type is image or text, open it in WebView else open in other
182    * installed application
183    */
184 
185   public static FileOpenRequest fileOpen(Context context, String fileType, String filePath, String fileName) {
186     FileOpenRequest result = new FileOpenRequest();
187     if (fileType == null) {
188       new UnreadableFileDialog(context, null).show();
189       result.mResult = FileOpenRequestResult.ERROR;
190     } else if (fileType.startsWith(IMAGE_TYPE) || fileType.startsWith(TEXT_TYPE)) {
191       Intent intent = new Intent(context, WebViewActivity.class);
192       intent.putExtra(ExoConstants.WEB_VIEW_URL, filePath);
193       intent.putExtra(ExoConstants.WEB_VIEW_TITLE, fileName);
194       intent.putExtra(ExoConstants.WEB_VIEW_MIME_TYPE, fileType);
195       intent.putExtra(ExoConstants.WEB_VIEW_ALLOW_JS, "false");
196       context.startActivity(intent);
197       result.mResult = FileOpenRequestResult.WEBVIEW;
198     } else {
199       result.mFileOpenController = new CompatibleFileOpen(context, fileType, filePath, fileName);
200       result.mResult = FileOpenRequestResult.EXTERNAL;
201     }
202     if (Log.LOGD)
203       Log.d(LOG_TAG, "File Open Result: "+result.mResult);
204     return result;
205   }
206 
207   /**
208    * Check whether the given Mime Type is forbidden. The list of forbidden types
209    * is in {@link ExoDocumentUtils.FORBIDDEN_TYPES}
210    * 
211    * @param mimeType
212    * @return true if the given Mime Type is in the list
213    */
214   public static boolean isForbidden(String mimeType) {
215     return Arrays.asList(FORBIDDEN_TYPES).contains(mimeType);
216   }
217 
218   /**
219    * Check if the device has an application to open this type of file.
220    * 
221    * @param context Context (mandatory)
222    * @param mimeType Mime Type to check (mandatory)
223    * @param url file url to guess the mime type from (optional)
224    * @return true if an application can open the given Mime Type
225    */
226   public static boolean isCallable(Context context, String mimeType, String url) throws IllegalArgumentException {
227     if (context == null || mimeType == null)
228       throw new IllegalArgumentException("Context or mime-type cannot be null.");
229 
230     Intent intent = new Intent(Intent.ACTION_VIEW);
231     intent.setType(mimeType.toLowerCase(Locale.US));
232     ComponentName activity = intent.resolveActivity(context.getPackageManager());
233 
234     if (activity == null && url != null) {
235       // Fallback on a guessed mime type if the first one doesn't work
236       String guessedMimeType = URLConnection.guessContentTypeFromName(url);
237       if (guessedMimeType != null) {
238         intent = new Intent(Intent.ACTION_VIEW);
239         intent.setType(guessedMimeType.toLowerCase(Locale.US));
240         activity = intent.resolveActivity(context.getPackageManager());
241       }
242     }
243 
244     return activity != null;
245   }
246 
247   public static String getFullFileType(String fileType) {
248     String docFileType = fileType;
249     if (fileType.startsWith(ExoDocumentUtils.AUDIO_TYPE)) {
250       docFileType = ExoDocumentUtils.ALL_AUDIO_TYPE;
251     } else if (fileType.startsWith(ExoDocumentUtils.VIDEO_TYPE)) {
252       docFileType = ExoDocumentUtils.ALL_VIDEO_TYPE;
253     } else if (fileType.startsWith(ExoDocumentUtils.IMAGE_TYPE)) {
254       docFileType = ExoDocumentUtils.ALL_IMAGE_TYPE;
255     } else if (fileType.startsWith(ExoDocumentUtils.TEXT_TYPE)) {
256       docFileType = ExoDocumentUtils.ALL_TEXT_TYPE;
257     }
258     return docFileType;
259   }
260 
261   public static boolean putFileToServerFromLocal(String url, File fileManager, String fileType) {
262     try {
263       url = url.replaceAll(" ", "%20");
264 
265       HttpPut put = new HttpPut(url);
266       FileEntity fileEntity = new FileEntity(fileManager, fileType);
267       put.setEntity(fileEntity);
268       fileEntity.setContentType(fileType);
269       HttpResponse response = ExoConnectionUtils.httpClient.execute(put);
270       int status = response.getStatusLine().getStatusCode();
271       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
272         return true;
273       } else {
274         return false;
275       }
276     } catch (IOException e) {
277       if (Log.LOGD)
278         Log.d(ExoDocumentUtils.class.getSimpleName(), e.getMessage(), Log.getStackTraceString(e));
279       return false;
280     } finally {
281       fileManager.delete();
282     }
283 
284   }
285 
286   public static void setRepositoryHomeUrl(String userName, String userHomeNodePath, String domain) {
287     String documentPath = getDocumenPath();
288     StringBuilder buffer = new StringBuilder();
289     buffer.append(domain);
290     buffer.append(documentPath);
291     buffer.append(userHomeNodePath);
292 
293     try {
294       WebdavMethod copy = new WebdavMethod("HEAD", buffer.toString());
295       int status = ExoConnectionUtils.httpClient.execute(copy).getStatusLine().getStatusCode();
296 
297       if (status >= 200 && status < 300) {
298         DocumentHelper.getInstance().setRepositoryHomeUrl(buffer.toString());
299       } else {
300         buffer = new StringBuilder(domain);
301         buffer.append(documentPath);
302         buffer.append("/");
303         buffer.append(userName);
304         DocumentHelper.getInstance().setRepositoryHomeUrl(buffer.toString());
305       }
306 
307     } catch (Exception e) {
308       // XXX cannot replace because WebdavMethod, httpclient.execute can throw
309       // exception
310       Log.e(LOG_TAG, e.getMessage(), Log.getStackTraceString(e));
311       DocumentHelper.getInstance().setRepositoryHomeUrl(null);
312     }
313   }
314 
315   /**
316    * Get the content (files and folders) of the given folder.
317    * 
318    * @param context
319    * @param file the folder to get content from
320    * @return an ExoFile corresponding to the parent folder with its children
321    *         ExoFile
322    * @throws IOException
323    */
324   public static ExoFile getPersonalDriveContent(Context context, ExoFile file) throws IOException {
325     SharedPreferences prefs = context.getSharedPreferences(ExoConstants.EXO_PREFERENCE, 0);
326     boolean isShowHidden = prefs.getBoolean(AccountSetting.getInstance().documentKey, true);
327     ExoFile folder = file;
328     String domain = AccountSetting.getInstance().getDomainName();
329     HttpResponse response = null;
330     String urlStr = null;
331     /*
332      * Put the current folder and its child list to mapping dictionary
333      */
334     if (DocumentHelper.getInstance().folderToChildrenMap == null) {
335       DocumentHelper.getInstance().folderToChildrenMap = new Bundle();
336     }
337 
338     // We're on the initial screen => list all drives
339     if ("".equals(file.name) && "".equals(file.path)) {
340       // personal drive
341       ArrayList<ExoFile> arrFilesTmp = new ArrayList<ExoFile>();
342       ArrayList<ExoFile> fileList = new ArrayList<ExoFile>();
343       StringBuffer buffer = new StringBuffer();
344       buffer.append(domain);
345       buffer.append(ExoConstants.DOCUMENT_DRIVE_PATH_REST);
346       buffer.append(ExoConstants.DOCUMENT_PERSONAL_DRIVER);
347       buffer.append(ExoConstants.DOCUMENT_PERSONAL_DRIVER_SHOW_PRIVATE);
348       buffer.append(isShowHidden);
349       urlStr = buffer.toString();
350       response = ExoConnectionUtils.getRequestResponse(urlStr);
351       fileList = getDrives(response);
352       if (fileList != null && fileList.size() > 0) {
353         arrFilesTmp.add(new ExoFile(ExoConstants.DOCUMENT_PERSONAL_DRIVER));
354         arrFilesTmp.addAll(fileList);
355       }
356       // general drive
357       buffer = new StringBuffer();
358       buffer.append(domain);
359       buffer.append(ExoConstants.DOCUMENT_DRIVE_PATH_REST);
360       buffer.append(ExoConstants.DOCUMENT_GENERAL_DRIVER);
361       urlStr = buffer.toString();
362       response = ExoConnectionUtils.getRequestResponse(urlStr);
363       fileList = getDrives(response);
364       if (fileList != null && fileList.size() > 0) {
365         arrFilesTmp.add(new ExoFile(ExoConstants.DOCUMENT_GENERAL_DRIVER));
366         arrFilesTmp.addAll(fileList);
367       }
368 
369       // group drive
370       buffer = new StringBuffer();
371       buffer.append(domain);
372       buffer.append(ExoConstants.DOCUMENT_DRIVE_PATH_REST);
373       buffer.append(ExoConstants.DOCUMENT_GROUP_DRIVER);
374       urlStr = buffer.toString();
375       response = ExoConnectionUtils.getRequestResponse(urlStr);
376       // "true" to generate the natural name for the folders in the group
377       fileList = getDrives(response, true);
378       if (fileList != null && fileList.size() > 0) {
379         arrFilesTmp.add(new ExoFile(ExoConstants.DOCUMENT_GROUP_DRIVER));
380         arrFilesTmp.addAll(fileList);
381       }
382 
383       // store the drives root folders
384       if (DocumentHelper.getInstance().folderToChildrenMap.containsKey(ExoConstants.DOCUMENT_JCR_PATH)) {
385         DocumentHelper.getInstance().folderToChildrenMap.remove(ExoConstants.DOCUMENT_JCR_PATH);
386         DocumentHelper.getInstance().folderToChildrenMap.putParcelableArrayList(ExoConstants.DOCUMENT_JCR_PATH, arrFilesTmp);
387       } else {
388         DocumentHelper.getInstance().folderToChildrenMap.putParcelableArrayList(ExoConstants.DOCUMENT_JCR_PATH, arrFilesTmp);
389       }
390 
391       // create an empty root folder to hold all the drives
392       folder.children = arrFilesTmp;
393     } else {
394       // We're in a drive or folder => list its content
395       urlStr = getDriverUrl(file);
396       urlStr = ExoUtils.encodeDocumentUrl(urlStr);
397       response = ExoConnectionUtils.getRequestResponse(urlStr);
398       folder = getContentOfFolder(response, file);
399       // store the children of the loaded folder
400       if (DocumentHelper.getInstance().folderToChildrenMap.containsKey(file.path)) {
401         DocumentHelper.getInstance().folderToChildrenMap.remove(file.path);
402         DocumentHelper.getInstance().folderToChildrenMap.putParcelableArrayList(file.path, new ArrayList<ExoFile>(folder.children));
403       } else
404         DocumentHelper.getInstance().folderToChildrenMap.putParcelableArrayList(file.path, new ArrayList<ExoFile>(folder.children));
405 
406     }
407 
408     return folder;
409 
410   }
411 
412   public static String fullURLofFile(String workSpaceName, String url) {
413     String domain = AccountSetting.getInstance().getDomainName();
414     StringBuffer buffer = new StringBuffer(domain);
415     buffer.append(ExoConstants.DOCUMENT_JCR_PATH);
416     buffer.append("/");
417     buffer.append(DocumentHelper.getInstance().repository);
418     buffer.append("/");
419     buffer.append(workSpaceName);
420     buffer.append(url);
421     return buffer.toString();
422 
423   }
424 
425   private static String getDocumenPath() {
426     StringBuilder documentPath = new StringBuilder();
427     documentPath.append(ExoConstants.DOCUMENT_JCR_PATH);
428     documentPath.append("/");
429     documentPath.append(DocumentHelper.getInstance().repository);
430     documentPath.append("/");
431     documentPath.append(ExoConstants.DOCUMENT_COLLABORATION);
432     return documentPath.toString();
433   }
434 
435   /**
436    * Get the list of folders in a drive, from the HTTP response.<br/>
437    * The response's body is an XM document that is parsed to extract the
438    * information of the folders. <br/>
439    * This method simply calls
440    * 
441    * <pre>
442    * ExoDocumentUtils.getDrives(response, false);
443    * </pre>
444    * 
445    * @param response the HttpResponse from where to extract the list of folders
446    * @return an ArrayList of ExoFile or an empty ArrayList if a problem happens
447    * @see ExoDocumentUtils.getDrives(HttpResponse response, boolean
448    *      isGroupDrive)
449    */
450   public static ArrayList<ExoFile> getDrives(HttpResponse response) {
451     return getDrives(response, false);
452   }
453 
454   /**
455    * Get the list of folders in a drive, from the HTTP response.<br/>
456    * The response's body is an XM document that is parsed to extract the
457    * information of the folders. <br/>
458    * If <i>isGroupDrive = true</i> , each folder's name is improved to be less
459    * technical, by calling <i>ExoFile#createNaturalName()</i>
460    * 
461    * @param response the HttpResponse from where to extract the list of folders
462    * @param isGroupDrive if <i>true</i> the file's natural name will be created
463    * @return an ArrayList of ExoFile or an empty ArrayList if a problem happens
464    */
465   public static ArrayList<ExoFile> getDrives(HttpResponse response, boolean isGroupDrive) {
466     // Initialize the blogEntries MutableArray that we declared in the
467     // header
468     ArrayList<ExoFile> folderArray = new ArrayList<ExoFile>();
469 
470     try {
471       Document obj_doc = null;
472       DocumentBuilderFactory doc_build_fact = null;
473       DocumentBuilder doc_builder = null;
474 
475       doc_build_fact = DocumentBuilderFactory.newInstance();
476       doc_builder = doc_build_fact.newDocumentBuilder();
477       InputStream is = ExoConnectionUtils.sendRequest(response);
478       if (is != null) {
479         obj_doc = doc_builder.parse(is);
480 
481         NodeList obj_nod_list = null;
482         if (null != obj_doc) {
483           obj_nod_list = obj_doc.getElementsByTagName("Folder");
484 
485           for (int i = 0; i < obj_nod_list.getLength(); i++) {
486             Node itemNode = obj_nod_list.item(i);
487             if (itemNode.getNodeType() == Node.ELEMENT_NODE) {
488               Element itemElement = (Element) itemNode;
489               ExoFile file = new ExoFile();
490               file.name = itemElement.getAttribute("name");
491               // if (Config.GD_INFO_LOGS_ENABLED)
492               Log.i(" Public file name", file.name);
493               file.workspaceName = itemElement.getAttribute("workspaceName");
494               file.driveName = file.name;
495               file.currentFolder = itemElement.getAttribute("currentFolder");
496               if (file.currentFolder == null)
497                 file.currentFolder = "";
498               file.isFolder = true;
499               // create the folder's natural name only for folders
500               // in the group drive
501               if (isGroupDrive)
502                 file.createNaturalName();
503               /*
504                * If file name is "Public", get path for it.
505                */
506               if (file.name.equals("Public")) {
507                 file.path = getRootDriverPath(file);
508               }
509 
510               folderArray.add(file);
511             }
512           }
513         }
514       }
515     } catch (ParserConfigurationException e) {
516       Log.e(" ParserConfigurationException ", e.getMessage());
517       folderArray = null;
518     } catch (SAXException e) {
519       Log.e(" SAXException ", e.getMessage());
520       folderArray = null;
521     } catch (IOException e) {
522       Log.e(" IOException ", e.getMessage());
523       folderArray = null;
524     }
525 
526     return folderArray;
527   }
528 
529   // return the driver url
530   private static String getDriverUrl(ExoFile file) {
531     String domain = AccountSetting.getInstance().getDomainName();
532     StringBuffer buffer = new StringBuffer(domain);
533     buffer.append(ExoConstants.DOCUMENT_FILE_PATH_REST);
534     buffer.append(file.driveName);
535     buffer.append(ExoConstants.DOCUMENT_WORKSPACE_NAME);
536     buffer.append(file.workspaceName);
537     buffer.append(ExoConstants.DOCUMENT_CURRENT_FOLDER);
538     buffer.append(file.currentFolder);
539 
540     return buffer.toString();
541   }
542 
543   // get path for driver folder (ex. Public/Private)
544   private static String getRootDriverPath(ExoFile file) {
545     String path = null;
546     String urlStr = getDriverUrl(file);
547     urlStr = ExoUtils.encodeDocumentUrl(urlStr);
548     Document obj_doc = null;
549     DocumentBuilderFactory doc_build_fact = null;
550     DocumentBuilder doc_builder = null;
551     try {
552       HttpResponse response = ExoConnectionUtils.getRequestResponse(urlStr);
553       doc_build_fact = DocumentBuilderFactory.newInstance();
554       doc_builder = doc_build_fact.newDocumentBuilder();
555       InputStream is = ExoConnectionUtils.sendRequest(response);
556       if (is != null) {
557         obj_doc = doc_builder.parse(is);
558 
559         if (null != obj_doc) {
560 
561           // Get folders
562           NodeList obj_nod_list = obj_doc.getElementsByTagName("Folder");
563           Node rootNode = obj_nod_list.item(0);
564           if (rootNode.getNodeType() == Node.ELEMENT_NODE) {
565             Element itemElement = (Element) rootNode;
566             path = fullURLofFile(ExoConstants.DOCUMENT_COLLABORATION, itemElement.getAttribute("path"));
567           }
568         }
569       }
570       return path;
571     } catch (ParserConfigurationException e) {
572       return null;
573     } catch (SAXException e) {
574       return null;
575     } catch (IOException e) {
576       return null;
577     }
578   }
579 
580   private static ExoFile getFileFromXMLElement(Element element, boolean isFolder) throws NullPointerException {
581     if (element == null)
582       throw new NullPointerException("Given element is null");
583 
584     ExoFile file = new ExoFile();
585     if (element.hasAttribute("title")) {
586       file.name = Html.fromHtml(element.getAttribute("title")).toString();
587     } else {
588       file.name = element.getAttribute("name");
589     }
590     file.workspaceName = element.getAttribute("workspaceName");
591     file.path = fullURLofFile(file.workspaceName, element.getAttribute("path"));
592     if (element.hasAttribute("driveName"))
593       file.driveName = element.getAttribute("driveName");
594     else
595       file.driveName = file.name;
596     file.currentFolder = element.getAttribute("currentFolder");
597     if (file.currentFolder == null)
598       file.currentFolder = "";
599     file.isFolder = isFolder;
600     if (element.hasAttribute("nodeType"))
601       file.nodeType = element.getAttribute("nodeType");
602 
603     String canRemove = element.getAttribute("canRemove");
604     file.canRemove = Boolean.parseBoolean(canRemove.trim());
605 
606     return file;
607   }
608 
609   /**
610    * Get a folder with its sub-files and sub-folders.<br/>
611    * Parse the XML response with format:
612    * 
613    * <pre>
614    * &lt;Folder canAddChild="bool" canRemove="bool" currentFolder="Name" driveName="DriveName" hasChild="bool" name="Name" nodeType="nt" path="..." title="Title" titlePath="Title" workspaceName="Name">
615    *  &lt;Folders>
616    *    &lt;Folder canAddChild="bool" canRemove="bool" currentFolder="Name" driveName="DriveName" hasChild="bool" name="Name" nodeType="nt" path="..." title="Title" titlePath="Title" workspaceName="Name"/>
617    *  &lt;/Folders>
618    *  &lt;Files>
619    *    &lt;File canRemove="bool" creator="username" dateCreated="Date" dateModified="Date" name="doc.jpg" nodeType="nt" path="..." size="0" title="doc.jpg" workspaceName="Name"/>
620    *  &lt;/Files>
621    * &lt;/Folder>
622    * </pre>
623    * 
624    * Example URL:
625    * 
626    * <pre>
627    * https://SERVER/rest/managedocument/getFoldersAndFiles?driveName=Personal%
628    * 20Documents&workspaceName=collaboration&currentFolder=Public
629    * </pre>
630    * 
631    * @param response the response that contains the XML entity
632    * @param file The folder to retrieve the content from
633    * @return an ExoFile that represents the content of the given folder
634    */
635   public static ExoFile getContentOfFolder(HttpResponse response, ExoFile file) {
636 
637     ExoFile folder = file;
638     ArrayList<ExoFile> childrenArray = new ArrayList<ExoFile>();
639 
640     Document obj_doc = null;
641     DocumentBuilderFactory doc_build_fact = null;
642     DocumentBuilder doc_builder = null;
643     try {
644       doc_build_fact = DocumentBuilderFactory.newInstance();
645       doc_builder = doc_build_fact.newDocumentBuilder();
646       InputStream is = ExoConnectionUtils.sendRequest(response);
647       if (is != null) {
648         obj_doc = doc_builder.parse(is);
649 
650         NodeList obj_nod_list = null;
651         if (null != obj_doc) {
652 
653           // Get folders
654           obj_nod_list = obj_doc.getElementsByTagName("Folder");
655           for (int i = 0; i < obj_nod_list.getLength(); i++) {
656             Node itemNode = obj_nod_list.item(i);
657             if (itemNode.getNodeType() == Node.ELEMENT_NODE) {
658               Element itemElement = (Element) itemNode;
659 
660               if (i == 0) { // The first element is always the root folder
661 
662                 // We copy properties from tmp to folder
663                 // to keep the pointer to the folder instance
664                 ExoFile tmp = getFileFromXMLElement(itemElement, true);
665                 // Unfortunate hack
666                 // The drive "Personal Documents" is a folder named "Private"
667                 // We do this to display the drive name in this case
668                 if ("Private".equals(tmp.name) && "".equals(tmp.currentFolder) && "Personal Documents".equals(tmp.driveName))
669                   folder.name = tmp.driveName;
670                 else
671                   folder.name = tmp.name;
672 
673                 folder.workspaceName = tmp.workspaceName;
674                 folder.path = tmp.path;
675                 folder.driveName = tmp.driveName;
676                 folder.currentFolder = tmp.currentFolder;
677                 folder.isFolder = true;
678                 // Cannot delete the root folder of a drive
679                 if (folder.isFolder && "".equals(folder.currentFolder))
680                   folder.canRemove = false;
681                 else
682                   folder.canRemove = tmp.canRemove;
683 
684               } else { // Folders of the root folder
685 
686                 ExoFile childFolder = getFileFromXMLElement(itemElement, true);
687                 childrenArray.add(childFolder);
688               }
689             }
690           }
691 
692           // Get files
693           obj_nod_list = obj_doc.getElementsByTagName("File");
694           for (int i = 0; i < obj_nod_list.getLength(); i++) {
695             Node itemNode = obj_nod_list.item(i);
696             if (itemNode.getNodeType() == Node.ELEMENT_NODE) {
697               Element itemElement = (Element) itemNode;
698               ExoFile childFile = getFileFromXMLElement(itemElement, false);
699               childrenArray.add(childFile);
700             }
701           }
702 
703         }
704       }
705       folder.children = childrenArray;
706       return folder;
707     } catch (ParserConfigurationException e) {
708       return null;
709     } catch (SAXException e) {
710       return null;
711     } catch (IOException e) {
712       return null;
713     }
714 
715   }
716 
717   /*
718    * Get document icon from content type
719    */
720 
721   public static int getIconFromType(String contentType) {
722     int id = R.drawable.documenticonforunknown;
723     if (contentType != null) {
724       if (contentType.indexOf(IMAGE_TYPE) >= 0)
725         id = R.drawable.documenticonforimage;
726       else if (contentType.indexOf(VIDEO_TYPE) >= 0)
727         id = R.drawable.documenticonforvideo;
728       else if (contentType.indexOf(AUDIO_TYPE) >= 0)
729         id = R.drawable.documenticonformusic;
730       else if (contentType.indexOf(MSWORD_TYPE) >= 0 || contentType.indexOf(OPEN_WORD_TYPE) >= 0)
731         id = R.drawable.documenticonforword;
732       else if (contentType.indexOf(PDF_TYPE) >= 0)
733         id = R.drawable.documenticonforpdf;
734       else if (contentType.indexOf(XLS_TYPE) >= 0 || contentType.indexOf(OPEN_XLS_TYPE) >= 0)
735         id = R.drawable.documenticonforxls;
736       else if (contentType.indexOf(POWERPOINT_TYPE) >= 0 || contentType.indexOf(OPEN_POWERPOINT_TYPE) >= 0)
737         id = R.drawable.documenticonforppt;
738       else if (contentType.indexOf(TEXT_TYPE) >= 0)
739         id = R.drawable.documenticonfortxt;
740     }
741 
742     return id;
743   }
744 
745   public static String getParentUrl(String url) {
746 
747     int index = url.lastIndexOf("/");
748     if (index > 0)
749       return url.substring(0, index);
750 
751     return "";
752   }
753 
754   public static String getLastPathComponent(String url) {
755 
756     int index = url.lastIndexOf("/");
757     if (index > 0)
758       return url.substring(url.lastIndexOf("/") + 1, url.length());
759 
760     return url;
761 
762   }
763 
764   public static boolean isContainSpecialChar(String str, String charSet) {
765 
766     Pattern patt = Pattern.compile(charSet);
767     Matcher matcher = patt.matcher(str);
768     return matcher.find();
769   }
770 
771   /**
772    * Delete remote folder or file
773    * 
774    * @param url the URL of the folder or file to delete
775    * @return true if the file was deleted, false otherwise
776    */
777   public static boolean deleteFile(String url) {
778     HttpResponse response;
779     try {
780       url = ExoUtils.encodeDocumentUrl(url);
781       WebdavMethod delete = new WebdavMethod("DELETE", url);
782       response = ExoConnectionUtils.httpClient.execute(delete);
783       int status = response.getStatusLine().getStatusCode();
784       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
785         return true;
786       } else
787         return false;
788 
789     } catch (IOException e) {
790       return false;
791     }
792 
793   }
794 
795   // Copy file/folder method
796   public static boolean copyFile(String source, String destination) {
797 
798     HttpResponse response;
799     try {
800       if (source.equals(destination)) {
801         return false;
802       }
803       source = ExoUtils.encodeDocumentUrl(source);
804       destination = ExoUtils.encodeDocumentUrl(destination);
805       WebdavMethod copy = new WebdavMethod("COPY", source, destination);
806       response = ExoConnectionUtils.httpClient.execute(copy);
807       int status = response.getStatusLine().getStatusCode();
808       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
809         return true;
810       } else
811         return false;
812 
813     } catch (IOException e) {
814       if (Log.LOGD)
815         Log.d(ExoDocumentUtils.class.getSimpleName(), e.getMessage(), Log.getStackTraceString(e));
816       return false;
817     }
818   }
819 
820   // Move file/folder method
821   public static boolean moveFile(String source, String destination) {
822     HttpResponse response;
823     try {
824       if (source.equals(destination)) {
825         return false;
826       }
827       source = ExoUtils.encodeDocumentUrl(source);
828       destination = ExoUtils.encodeDocumentUrl(destination);
829       WebdavMethod move = new WebdavMethod("MOVE", source, destination);
830       response = ExoConnectionUtils.httpClient.execute(move);
831       int status = response.getStatusLine().getStatusCode();
832       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
833         return true;
834       } else
835         return false;
836 
837     } catch (IOException e) {
838       return false;
839     }
840 
841   }
842 
843   public static boolean renameFolder(String source, String destination) {
844     HttpResponse response;
845     try {
846       source = ExoUtils.encodeDocumentUrl(source);
847       destination = ExoUtils.encodeDocumentUrl(destination);
848       WebdavMethod create = new WebdavMethod("HEAD", destination);
849       response = ExoConnectionUtils.httpClient.execute(create);
850       int status = response.getStatusLine().getStatusCode();
851       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
852         return true;
853       } else {
854         WebdavMethod move = new WebdavMethod("MOVE", source, destination);
855         response = ExoConnectionUtils.httpClient.execute(move);
856         status = response.getStatusLine().getStatusCode();
857         if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
858           return true;
859         } else {
860           return false;
861         }
862 
863       }
864     } catch (IOException e) {
865       return false;
866     }
867 
868   }
869 
870   public static boolean createFolder(String destination) {
871     HttpResponse response;
872     try {
873 
874       destination = ExoUtils.encodeDocumentUrl(destination);
875       WebdavMethod create = new WebdavMethod("HEAD", destination);
876       response = ExoConnectionUtils.httpClient.execute(create);
877       int status = response.getStatusLine().getStatusCode();
878       if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
879         return true;
880       } else {
881         create = new WebdavMethod("MKCOL", destination);
882         response = ExoConnectionUtils.httpClient.execute(create);
883         status = response.getStatusLine().getStatusCode();
884 
885         if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
886           return true;
887         } else
888           return false;
889       }
890 
891     } catch (Exception e) {
892       // XXX catch null of destination, WebdavMethod initial, httpclient
893       // exception
894       Log.e(LOG_TAG, e.getMessage(), e);
895       return false;
896     }
897   }
898 
899   /**
900    * Returns a DocumentInfo with info coming from the file at the given URI
901    * 
902    * @param document the URI of a file or a content
903    * @param context
904    * @return a DocumentInfo or null if an error occurs
905    */
906   public static DocumentInfo documentInfoFromUri(Uri document, Context context) {
907     if (document == null)
908       return null;
909 
910     if (document.toString().startsWith("content://")) {
911       /*
912        *  Some apps send fake content:// URI with real file:// URI inside
913        *  E.g. open ASTRO File Manager > View File > Share :
914        *  
915        *  content://authority/-1/1/file:///sdcard/path/file.jpg/ACTUAL/123
916        *  
917        *  Then we extract the real URI and pass it to documentFromFileUri(...)
918        */ 
919       String decodedUri = Uri.decode(document.toString());
920       int fileIdx = decodedUri.indexOf("file://");
921       if (fileIdx > -1) {
922         long id = -1;
923         try {
924           id = ContentUris.parseId(document);
925         } catch (NumberFormatException e) {
926           Log.e(LOG_TAG, e.getMessage(), e);
927         } catch (UnsupportedOperationException e) {
928           Log.e(LOG_TAG, e.getMessage(), e);
929         }
930         String fileUri = decodedUri.substring(fileIdx);
931         fileUri = fileUri.replaceAll("(/ACTUAL/)(" + id + ")", "");
932         return documentFromFileUri(Uri.parse(fileUri));
933       } else {
934         return documentFromContentUri(document, context);
935       }
936     } else if (document.toString().startsWith("file://")) {
937       return documentFromFileUri(document);
938     } else {
939       return null; // other formats not supported
940     }
941   }
942 
943   /**
944    * Gets a DocumentInfo with info coming from the document at the given URI.
945    * 
946    * @param contentUri the content URI of the document (content:// ...)
947    * @param context
948    * @return a DocumentInfo or null if an error occurs
949    */
950   public static DocumentInfo documentFromContentUri(Uri contentUri, Context context) {
951     if (contentUri == null)
952       return null;
953 
954     try {
955       ContentResolver cr = context.getContentResolver();
956       Cursor c = cr.query(contentUri, null, null, null, null);
957       int sizeIndex = c.getColumnIndex(OpenableColumns.SIZE);
958       int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
959       int orientIndex = c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION);
960       c.moveToFirst();
961 
962       DocumentInfo document = new DocumentInfo();
963       document.documentName = c.getString(nameIndex);
964       document.documentSizeKb = c.getLong(sizeIndex) / 1024;
965       document.documentData = cr.openInputStream(contentUri);
966       document.documentMimeType = cr.getType(contentUri);
967       if (orientIndex != -1) { // if found orientation column
968         document.orientationAngle = c.getInt(orientIndex);
969       }
970       return document;
971     } catch (FileNotFoundException e) {
972       Log.d(LOG_TAG, e.getClass().getSimpleName(), e.getLocalizedMessage());
973     } catch (Exception e) {
974       Log.e(LOG_TAG, "Cannot retrieve the content at " + contentUri);
975       if (Log.LOGD)
976         Log.d(LOG_TAG, e.getMessage() + "\n" + Log.getStackTraceString(e));
977     }
978     return null;
979   }
980 
981   /**
982    * Gets a DocumentInfo with info coming from the file at the given URI.
983    * 
984    * @param fileUri the file URI (file:// ...)
985    * @return a DocumentInfo or null if an error occurs
986    */
987   public static DocumentInfo documentFromFileUri(Uri fileUri) {
988     if (fileUri == null)
989       return null;
990 
991     try {
992       URI uri = new URI(fileUri.toString());
993       File file = new File(uri);
994 
995       DocumentInfo document = new DocumentInfo();
996       document.documentName = file.getName();
997       document.documentSizeKb = file.length() / 1024;
998       document.documentData = new FileInputStream(file);
999       // Guess the mime type in 2 ways
1000       try {
1001         // 1) by inspecting the file's first bytes
1002         document.documentMimeType = URLConnection.guessContentTypeFromStream(document.documentData);
1003       } catch (IOException e) {
1004         document.documentMimeType = null;
1005       }
1006       if (document.documentMimeType == null) {
1007         // 2) if it fails, by stripping the extension of the filename
1008         // and getting the mime type from it
1009         String extension = "";
1010         int dotPos = document.documentName.lastIndexOf('.');
1011         if (0 <= dotPos)
1012           extension = document.documentName.substring(dotPos + 1);
1013         document.documentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
1014       }
1015       // Get the orientation angle from the EXIF properties
1016       if ("image/jpeg".equals(document.documentMimeType))
1017         document.orientationAngle = getExifOrientationAngleFromFile(file.getAbsolutePath());
1018       return document;
1019     } catch (URISyntaxException e) {
1020       Log.e(LOG_TAG, "Cannot retrieve the file at " + fileUri);
1021       if (Log.LOGD)
1022         Log.d(LOG_TAG, e.getMessage() + "\n" + Log.getStackTraceString(e));
1023     } catch (FileNotFoundException e) {
1024       Log.e(LOG_TAG, "Cannot retrieve the file at " + fileUri);
1025       if (Log.LOGD)
1026         Log.d(LOG_TAG, e.getMessage() + "\n" + Log.getStackTraceString(e));
1027     }
1028     return null;
1029   }
1030 
1031   /**
1032    * Delete the Files at the given paths
1033    * 
1034    * @param files a list of file paths
1035    * @return true if all files were deleted, false otherwise
1036    */
1037   public static boolean deleteLocalFiles(List<String> files) {
1038     boolean result = true;
1039     if (files != null) {
1040       for (String filePath : files) {
1041         File f = new File(filePath);
1042         boolean del = f.delete();
1043         Log.d(LOG_TAG, "File " + f.getName() + " deleted: " + (del ? "YES" : "NO"));
1044         result &= del;
1045       }
1046     }
1047     return result;
1048   }
1049 
1050   /**
1051    * On Platform 4.1-M2, the upload service renames the uploaded file. Therefore
1052    * the link to this file in the activity becomes incorrect. To fix this, we
1053    * rename the file before upload so the same name is used in the activity.
1054    * 
1055    * @param originalName the name to clean
1056    * @return a String without forbidden characters
1057    */
1058   public static String cleanupFilename(String originalName) {
1059     final String TILDE_HYPHENS_COLONS_SPACES = "[~_:\\s]";
1060     final String MULTIPLE_HYPHENS = "-{2,}";
1061     final String FORBIDDEN_CHARS = "[`!@#\\$%\\^&\\*\\|;\"'<>/\\\\\\[\\]\\{\\}\\(\\)\\?,=\\+\\.]+";
1062     String name = originalName;
1063     String ext = "";
1064     int lastDot = name.lastIndexOf('.');
1065     if (lastDot > 0 && lastDot < name.length()) {
1066       ext = name.substring(lastDot); // the ext with the dot
1067       name = name.substring(0, lastDot); // the name before the ext
1068     }
1069     // [~_:\s] Replaces ~ _ : and spaces by -
1070     name = Pattern.compile(TILDE_HYPHENS_COLONS_SPACES).matcher(name).replaceAll("-");
1071     // [`!@#\$%\^&\*\|;"'<>/\\\[\]\{\}\(\)\?,=\+\.]+ Deletes forbidden chars
1072     name = Pattern.compile(FORBIDDEN_CHARS).matcher(name).replaceAll("");
1073     // Converts accents to regular letters
1074     name = Normalizer.normalize(name, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
1075     // Replaces upper case characters by lower case
1076     // Locale loc = new
1077     // Locale(SettingUtils.getPrefsLanguage(getApplicationContext()));
1078     name = name.toLowerCase(Locale.getDefault());
1079     // Remove consecutive -
1080     name = Pattern.compile(MULTIPLE_HYPHENS).matcher(name).replaceAll("-");
1081     // Save
1082     return (name + ext);
1083   }
1084 
1085   public static final int ROTATION_0   = 0;
1086 
1087   public static final int ROTATION_90  = 90;
1088 
1089   public static final int ROTATION_180 = 180;
1090 
1091   public static final int ROTATION_270 = 270;
1092 
1093   /**
1094    * Get the EXIF orientation of the given file
1095    * 
1096    * @param filePath
1097    * @return an int in ExoDocumentUtils.ROTATION_[0 , 90 , 180 , 270]
1098    */
1099   public static int getExifOrientationAngleFromFile(String filePath) {
1100     int ret = ROTATION_0;
1101     try {
1102       ret = new ExifInterface(filePath).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
1103       switch (ret) {
1104       case ExifInterface.ORIENTATION_ROTATE_90:
1105         ret = ROTATION_90;
1106         break;
1107       case ExifInterface.ORIENTATION_ROTATE_180:
1108         ret = ROTATION_180;
1109         break;
1110       case ExifInterface.ORIENTATION_ROTATE_270:
1111         ret = ROTATION_270;
1112         break;
1113       default:
1114         break;
1115       }
1116     } catch (IOException e) {
1117       if (Log.LOGD)
1118         Log.d(ExoDocumentUtils.class.getSimpleName(), e.getMessage(), Log.getStackTraceString(e));
1119     }
1120     return ret;
1121   }
1122 
1123   /**
1124    * Rotate the bitmap at its correct orientation
1125    * 
1126    * @param filePath the file where the bitmap is stored
1127    * @param source the bitmap itself
1128    * @return the bitmap rotated with
1129    *         {@link ExoDocumentUtils.rotateBitmapByAngle(Bitmap, int)}
1130    */
1131   public static Bitmap rotateBitmapToNormal(String filePath, Bitmap source) {
1132     Bitmap ret = source;
1133 
1134     int orientation = getExifOrientationAngleFromFile(filePath);
1135     // Sometimes we get an orientation = 1
1136     // To avoid a 1º rotation,
1137     // we rotate only when the orientation is exactly 90º or 180º or 270º
1138     if (orientation == ROTATION_90 || orientation == ROTATION_180 || orientation == ROTATION_270) {
1139       ret = rotateBitmapByAngle(source, orientation);
1140     }
1141     return ret;
1142   }
1143 
1144   /**
1145    * Rotate the bitmap by a certain angle. Uses {@link Matrix#postRotate(int)}
1146    * 
1147    * @param source the bitmap to rotate
1148    * @param angle the rotation angle
1149    * @return a new rotated bitmap
1150    */
1151   public static Bitmap rotateBitmapByAngle(Bitmap source, int angle) {
1152     Bitmap ret = source;
1153     int w, h;
1154     w = source.getWidth();
1155     h = source.getHeight();
1156     Matrix matrix = new Matrix();
1157     matrix.postRotate(angle);
1158     try {
1159       ret = Bitmap.createBitmap(source, 0, 0, w, h, matrix, true);
1160     } catch (OutOfMemoryError e) {
1161       Log.d(ExoDocumentUtils.class.getSimpleName(), "Exception : ", e, Log.getStackTraceString(e));
1162     }
1163     return ret;
1164   }
1165   
1166   private static String permissionForCode(int permCode) {
1167     String permission = null;
1168     switch (permCode) {
1169     case ExoConstants.REQUEST_TAKE_PICTURE_WITH_CAMERA:
1170       // We store the captured image on disk, so we need the WRITE_EXTERNAL_STORAGE permission
1171       permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
1172       break;
1173     case ExoConstants.REQUEST_PICK_IMAGE_FROM_GALLERY:
1174       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
1175         // On Jelly Bean and after, return the actual READ_EXTERNAL_STORAGE permission 
1176         permission = permissionReadExternalStorage();
1177       else
1178         // Otherwise returning WRITE_EXTERNAL_STORAGE implicitly grants READ_EXTERNAL_STORAGE
1179         permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
1180       break;
1181       default:
1182         throw new IllegalArgumentException("Given permission code is incorrect: "+permCode);
1183     }
1184     return permission;
1185   }
1186   
1187   @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1188   private static String permissionReadExternalStorage() {
1189     return Manifest.permission.READ_EXTERNAL_STORAGE;
1190   }
1191   
1192   /**
1193    * Check whether the application needs to request the permission required by the activity.
1194    * If yes, then the permission is requested (via {@link ActivityCompat#requestPermissions(Activity, String[], int)}).
1195    * 
1196    * @param caller The activity that requires the permission. Must implement {@link OnRequestPermissionsResultCallback}.
1197    * @param permissionCode The code defined internally, e.g. {@link ExoConstants.REQUEST_PICK_IMAGE_FROM_GALLERY}.
1198    * @return true if the permission has been requested <br/>
1199    *         false if the permission was already granted
1200    */
1201   public static boolean didRequestPermission(Activity caller, int permissionCode) {
1202     if (caller == null || !(caller instanceof OnRequestPermissionsResultCallback)) 
1203       throw new IllegalArgumentException("Caller activity must implement OnRequestPermissionsResultCallback");
1204     
1205     boolean res = false;
1206     String permission = permissionForCode(permissionCode);
1207     int check = ContextCompat.checkSelfPermission(caller, permission);
1208     if (check != PackageManager.PERMISSION_GRANTED) {
1209       res = true;
1210       ActivityCompat.requestPermissions(caller, new String[]{permission}, permissionCode);
1211     }
1212     return res;
1213   }
1214   
1215   /**
1216    * Check whether the request for the specified permission should be explained to the user.
1217    * Calls {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity, String)}.
1218    * 
1219    * @param activity The activity that requires the permission.
1220    * @param permCode The code defined internally, e.g. {@link ExoConstants.REQUEST_PICK_IMAGE_FROM_GALLERY}.
1221    * @return true if the user should receive more information about the permission request
1222    */
1223   public static boolean shouldDisplayExplanation(Activity activity, int permCode) {
1224     if (activity == null)
1225       throw new IllegalArgumentException("Caller activity must not be null");
1226     String permission = permissionForCode(permCode);
1227     return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
1228   }
1229 
1230 
1231   public static class DocumentInfo {
1232 
1233     public String      documentName;
1234 
1235     public long        documentSizeKb;
1236 
1237     public InputStream documentData;
1238 
1239     public String      documentMimeType;
1240 
1241     public int         orientationAngle = ROTATION_0;
1242 
1243     @Override
1244     public String toString() {
1245       return String.format(Locale.US, "File %s [%s - %s KB]", documentName, documentMimeType, documentSizeKb);
1246     }
1247 
1248     public void closeDocStream() {
1249       if (documentData != null)
1250         try {
1251           documentData.close();
1252         } catch (IOException e) {
1253           if (Log.LOGD)
1254             Log.d(LOG_TAG, Log.getStackTraceString(e));
1255         }
1256     }
1257 
1258     /**
1259      * On Platform 4.1-M2, the upload service renames the uploaded file.
1260      * Therefore the link to this file in the activity becomes incorrect. To fix
1261      * this, we rename the file before upload so the same name is used in the
1262      * activity.
1263      */
1264     public void cleanupFilename(Context context) {
1265       final String TILDE_HYPHENS_COLONS_SPACES = "[~_:\\s]";
1266       final String MULTIPLE_HYPHENS = "-{2,}";
1267       final String FORBIDDEN_CHARS = "[`!@#\\$%\\^&\\*\\|;\"'<>/\\\\\\[\\]\\{\\}\\(\\)\\?,=\\+\\.]+";
1268       String name = documentName;
1269       String ext = "";
1270       int lastDot = name.lastIndexOf('.');
1271       if (lastDot > 0 && lastDot < name.length()) {
1272         ext = name.substring(lastDot); // the ext with the dot
1273         name = name.substring(0, lastDot); // the name before the ext
1274       }
1275       // [~_:\s] Replaces ~ _ : and spaces by -
1276       name = Pattern.compile(TILDE_HYPHENS_COLONS_SPACES).matcher(name).replaceAll("-");
1277       // [`!@#\$%\^&\*\|;"'<>/\\\[\]\{\}\(\)\?,=\+\.]+ Deletes forbidden chars
1278       name = Pattern.compile(FORBIDDEN_CHARS).matcher(name).replaceAll("");
1279       // Converts accents to regular letters
1280       name = Normalizer.normalize(name, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
1281       // Replaces upper case characters by lower case
1282       Locale loc = new Locale(SettingUtils.getPrefsLanguage(context.getApplicationContext()));
1283       name = name.toLowerCase(loc == null ? Locale.getDefault() : loc);
1284       // Remove consecutive -
1285       name = Pattern.compile(MULTIPLE_HYPHENS).matcher(name).replaceAll("-");
1286       // Save
1287       documentName = name + ext;
1288     }
1289   }
1290 }