Bläddra i källkod

Fix crash opening or sending files in Android 7

# Conflicts:
#	res/values/setup.xml
#	src/com/owncloud/android/ui/activity/Preferences.java
David A. Velasco 8 år sedan
förälder
incheckning
aa0e4b8cc8

+ 11 - 0
AndroidManifest.xml

@@ -161,6 +161,17 @@
             </intent-filter>
         </provider>
 
+        <!-- new provider used to generate URIs without file:// scheme (forbidden from Android 7) -->
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="@string/file_provider_authority"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/exposed_filepaths" />
+        </provider>
+
         <activity
             android:name=".authentication.AuthenticatorActivity"
             android:exported="true"

+ 2 - 2
res/values/setup.xml

@@ -2,10 +2,10 @@
 <resources>
     <!-- App name  and other strings-->
     <string name="app_name">Nextcloud</string>
-    <string name="account_type">nextcloud</string>	<!-- better if was a domain name; but changing it now would require
-    migrate accounts when the app is updated -->
+    <string name="account_type">nextcloud</string>	<!-- better if was a domain name; but changing it now would require migrate accounts when the app is updated -->
     <string name="authority">org.nextcloud</string>	<!-- better if was the app package with ".provider" appended ; it identifies the provider -->
     <string name="document_provider_authority">org.nextcloud.documents</string>
+    <string name="file_provider_authority">org.nextcloud.files</string>
     <string name ="db_file">nextcloud.db</string>
     <string name ="db_name">nextcloud</string>
     <string name ="data_folder">nextcloud</string>

+ 6 - 0
res/xml/exposed_filepaths.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <external-path name="file" path="/" />
+    <!-- yes, valid for ALL external storage and not only our app folder, since we can't use @string/data_folder
+    as a value for 'path' attribute; in practice, we will only generate URIs in our folders, of course -->
+</paths>

+ 41 - 0
src/com/owncloud/android/datamodel/OCFile.java

@@ -24,10 +24,16 @@ package com.owncloud.android.datamodel;
 
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.content.FileProvider;
+import android.webkit.MimeTypeMap;
 
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.network.WebdavUtils;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.utils.MimeType;
 
@@ -94,6 +100,14 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     private Uri mLocalUri;
 
 
+    /**
+     * Exportable URI to the local path of the file contents, if stored in the device.
+     *
+     * Cached after first call, until changed.
+     */
+    private Uri mExposedFileUri;
+
+
     /**
      * Create new {@link OCFile} with given path.
      * <p/>
@@ -244,6 +258,32 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         return mLocalUri;
     }
 
+    public Uri getExposedFileUri(Context context) {
+        if (mLocalPath == null || mLocalPath.length() == 0) {
+            return null;
+        }
+        if (mExposedFileUri == null) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+                // TODO - use FileProvider with any Android version, with deeper testing -> 2.2.0
+                mExposedFileUri = Uri.parse(
+                    ContentResolver.SCHEME_FILE + "://" + WebdavUtils.encodePath(mLocalPath)
+                );
+            } else {
+                // Use the FileProvider to get a content URI
+                try {
+                    mExposedFileUri = FileProvider.getUriForFile(
+                        context,
+                        context.getString(R.string.file_provider_authority),
+                        new File(mLocalPath)
+                    );
+                } catch (IllegalArgumentException e) {
+                    Log_OC.e(TAG, "File can't be exported");
+                }
+            }
+        }
+        return mExposedFileUri;
+    }
+
     /**
      * Can be used to set the path where the file is stored
      *
@@ -252,6 +292,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     public void setStoragePath(String storage_path) {
         mLocalPath = storage_path;
         mLocalUri = null;
+        mExposedFileUri = null;
     }
 
     /**

+ 1 - 1
src/com/owncloud/android/ui/activity/ComponentsGetter.java

@@ -21,7 +21,7 @@
 package com.owncloud.android.ui.activity;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.files.FileOperationsHelper;
+import com.owncloud.android.ui.helpers.FileOperationsHelper;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;

+ 1 - 1
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -41,7 +41,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.FileOperationsHelper;
+import com.owncloud.android.ui.helpers.FileOperationsHelper;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;

+ 5 - 4
src/com/owncloud/android/files/FileOperationsHelper.java → src/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -20,7 +20,7 @@
  *
  */
 
-package com.owncloud.android.files;
+package com.owncloud.android.ui.helpers;
 
 import android.accounts.Account;
 import android.content.ActivityNotFoundException;
@@ -145,7 +145,6 @@ public class FileOperationsHelper {
     public void openFile(OCFile file) {
         if (file != null) {
             String storagePath = file.getStoragePath();
-            String encodedStoragePath = WebdavUtils.encodePath(storagePath);
 			Uri uri = Uri.parse("file://" + encodedStoragePath);
 
             Intent openFileWithIntent = null;
@@ -495,11 +494,13 @@ public class FileOperationsHelper {
     public void sendDownloadedFile(OCFile file) {
         if (file != null) {
             String storagePath = file.getStoragePath();
-            String encodedStoragePath = WebdavUtils.encodePath(storagePath);
             Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
             // set MimeType
             sendIntent.setType(file.getMimetype());
-            sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + encodedStoragePath));
+            sendIntent.putExtra(
+                Intent.EXTRA_STREAM,
+                file.getExposedFileUri(mFileActivity)
+            );
             sendIntent.putExtra(Intent.ACTION_SEND, true);      // Send Action
 
             // Show dialog, without the own app

+ 5 - 18
src/com/owncloud/android/utils/FileStorageUtils.java

@@ -65,23 +65,10 @@ public class FileStorageUtils {
     public static Integer mSortOrder = SORT_NAME;
     public static Boolean mSortAscending = true;
 
-    /**
-     * Takes a full path to owncloud file and removes beginning which is path to ownload data folder.
-     * If fullPath does not start with that folder, fullPath is returned as is.
-     */
-    public static final String removeDataFolderPath(String fullPath) {
-        File sdCard = Environment.getExternalStorageDirectory();
-        String dataFolderPath = sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/";
-        if(fullPath.indexOf(dataFolderPath) == 0) {
-            return fullPath.substring(dataFolderPath.length());
-        }
-        return fullPath;
-    }
-    
     /**
      * Get local owncloud storage path for accountName.
      */
-    public static final String getSavePath(String accountName) {
+    public static String getSavePath(String accountName) {
         return MainApp.getStoragePath()
                 + File.separator
                 + MainApp.getDataFolder()
@@ -96,14 +83,14 @@ public class FileStorageUtils {
      * corresponding local path (in local owncloud storage) to remote uploaded
      * file.
      */
-    public static final String getDefaultSavePathFor(String accountName, OCFile file) {
+    public static String getDefaultSavePathFor(String accountName, OCFile file) {
         return getSavePath(accountName) + file.getRemotePath();
     }
 
     /**
      * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
      */
-    public static final String getTemporalPath(String accountName) {
+    public static String getTemporalPath(String accountName) {
         return MainApp.getStoragePath()
                 + File.separator
                 + MainApp.getDataFolder()
@@ -121,12 +108,12 @@ public class FileStorageUtils {
      * @param accountName not used. can thus be null.
      * @return Optimistic number of available bytes (can be less)
      */
-    public static final long getUsableSpace(String accountName) {
+    public static long getUsableSpace(String accountName) {
         File savePath = new File(MainApp.getStoragePath());
         return savePath.getUsableSpace();
     }
     
-    public static final String getLogPath()  {
+    public static String getLogPath()  {
         return MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + File.separator + "log";
     }