Browse Source

Merge pull request #9228 from nextcloud/manage-all-files

Ditch scoped storage in favor of MANAGE_EXTERNAL_STORAGE
Álvaro Brey 3 năm trước cách đây
mục cha
commit
38e78b3f37
22 tập tin đã thay đổi với 123 bổ sung98 xóa
  1. 8 8
      src/androidTest/java/com/nextcloud/client/EndToEndRandomIT.java
  2. 23 1
      src/androidTest/java/com/nextcloud/client/GrantStoragePermissionRule.kt
  3. 2 2
      src/androidTest/java/com/owncloud/android/AbstractIT.java
  4. 2 2
      src/androidTest/java/com/owncloud/android/DownloadIT.java
  5. 17 17
      src/androidTest/java/com/owncloud/android/UploadIT.java
  6. 2 3
      src/androidTest/java/com/owncloud/android/utils/FileUtilTest.kt
  7. 3 6
      src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt
  8. 0 8
      src/debug/AndroidManifest.xml
  9. 4 1
      src/main/AndroidManifest.xml
  10. 0 2
      src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt
  11. 3 17
      src/main/java/com/nextcloud/client/onboarding/OnboardingServiceImpl.kt
  12. 3 5
      src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
  13. 2 5
      src/main/java/com/owncloud/android/operations/RenameFileOperation.java
  14. 2 3
      src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java
  15. 1 1
      src/main/java/com/owncloud/android/services/OperationsService.java
  16. 5 3
      src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  17. 1 1
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  18. 1 1
      src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java
  19. 1 1
      src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java
  20. 1 2
      src/main/java/com/owncloud/android/utils/FileStorageUtils.java
  21. 40 9
      src/main/java/com/owncloud/android/utils/PermissionUtil.kt
  22. 2 0
      src/main/res/values/strings.xml

+ 8 - 8
src/androidTest/java/com/nextcloud/client/EndToEndRandomIT.java

@@ -378,13 +378,13 @@ public class EndToEndRandomIT extends AbstractOnServerIT {
     public void testUploadWithCopy() throws Exception {
         init();
 
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          currentFolder.getRemotePath() + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_COPY);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
                                                                                       "nonEmpty.txt");
 
@@ -396,13 +396,13 @@ public class EndToEndRandomIT extends AbstractOnServerIT {
     public void testUploadWithMove() throws Exception {
         init();
 
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          currentFolder.getRemotePath() + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_MOVE);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
                                                                                       "nonEmpty.txt");
 
@@ -414,13 +414,13 @@ public class EndToEndRandomIT extends AbstractOnServerIT {
     public void testUploadWithForget() throws Exception {
         init();
 
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          currentFolder.getRemotePath() + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_FORGET);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
                                                                                       "nonEmpty.txt");
 
@@ -432,13 +432,13 @@ public class EndToEndRandomIT extends AbstractOnServerIT {
     public void testUploadWithDelete() throws Exception {
         init();
 
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          currentFolder.getRemotePath() + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_DELETE);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
                                                                                       "nonEmpty.txt");
 

+ 23 - 1
src/androidTest/java/com/nextcloud/client/GrantStoragePermissionRule.kt

@@ -21,14 +21,36 @@
 
 package com.nextcloud.client
 
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
 import com.owncloud.android.utils.PermissionUtil
 import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
 
 class GrantStoragePermissionRule private constructor() {
 
     companion object {
         @JvmStatic
-        fun grant(): TestRule = GrantPermissionRule.grant(PermissionUtil.getExternalStoragePermission())
+        fun grant(): TestRule = when {
+            Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> GrantPermissionRule.grant(
+                PermissionUtil
+                    .getExternalStoragePermission()
+            )
+            else -> GrantManageExternalStoragePermissionRule()
+        }
+    }
+
+    private class GrantManageExternalStoragePermissionRule : TestRule {
+        override fun apply(base: Statement, description: Description): Statement = object : Statement() {
+            override fun evaluate() {
+                InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
+                    "appops set --uid ${InstrumentationRegistry.getInstrumentation().targetContext.packageName} " +
+                        "MANAGE_EXTERNAL_STORAGE allow"
+                )
+                base.evaluate()
+            }
+        }
     }
 }

+ 2 - 2
src/androidTest/java/com/owncloud/android/AbstractIT.java

@@ -205,7 +205,7 @@ public abstract class AbstractIT {
     }
 
     protected static void createDummyFiles() throws IOException {
-        File tempPath = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext));
+        File tempPath = new File(FileStorageUtils.getTemporalPath(account.name));
         if (!tempPath.exists()) {
             assertTrue(tempPath.mkdirs());
         }
@@ -243,7 +243,7 @@ public abstract class AbstractIT {
     }
 
     public static File createFile(String name, int iteration) throws IOException {
-        File file = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + File.separator + name);
+        File file = new File(FileStorageUtils.getTemporalPath(account.name) + File.separator + name);
         if (!file.getParentFile().exists()) {
             assertTrue(file.getParentFile().mkdirs());
         }

+ 2 - 2
src/androidTest/java/com/owncloud/android/DownloadIT.java

@@ -73,13 +73,13 @@ public class DownloadIT extends AbstractOnServerIT {
 
     @Test
     public void verifyDownload() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload);
 
-        OCUpload ocUpload2 = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload2 = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                           FOLDER + "nonEmpty2.txt",
                                           account.name);
 

+ 17 - 17
src/androidTest/java/com/owncloud/android/UploadIT.java

@@ -122,7 +122,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testEmptyUpload() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "empty.txt",
                                          account.name);
 
@@ -131,7 +131,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testNonEmptyUpload() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
@@ -140,13 +140,13 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadWithCopy() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_COPY);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
 
         assertTrue(originalFile.exists());
@@ -156,13 +156,13 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadWithMove() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_MOVE);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
 
         assertFalse(originalFile.exists());
@@ -172,13 +172,13 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadWithForget() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_FORGET);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
 
         assertTrue(originalFile.exists());
@@ -188,13 +188,13 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadWithDelete() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
         uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_DELETE);
 
-        File originalFile = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/nonEmpty.txt");
+        File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
 
         assertFalse(originalFile.exists());
@@ -204,7 +204,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testChunkedUpload() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/chunkedFile.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/chunkedFile.txt",
                                          FOLDER + "chunkedFile.txt", account.name);
 
         uploadOCUpload(ocUpload);
@@ -212,7 +212,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadInNonExistingFolder() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "2/3/4/1.txt", account.name);
 
         uploadOCUpload(ocUpload);
@@ -220,7 +220,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadOnChargingOnlyButNotCharging() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "notCharging.txt", account.name);
         ocUpload.setWhileChargingOnly(true);
 
@@ -268,7 +268,7 @@ public class UploadIT extends AbstractOnServerIT {
             }
         };
 
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "charging.txt", account.name);
         ocUpload.setWhileChargingOnly(true);
 
@@ -308,7 +308,7 @@ public class UploadIT extends AbstractOnServerIT {
                 return new Connectivity(true, false, false, true);
             }
         };
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "noWifi.txt", account.name);
         ocUpload.setUseWifiOnly(true);
 
@@ -338,7 +338,7 @@ public class UploadIT extends AbstractOnServerIT {
 
     @Test
     public void testUploadOnWifiOnlyAndWifi() {
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "wifi.txt", account.name);
         ocUpload.setWhileChargingOnly(true);
 
@@ -387,7 +387,7 @@ public class UploadIT extends AbstractOnServerIT {
                 return new Connectivity(true, true, true, true);
             }
         };
-        OCUpload ocUpload = new OCUpload(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + "/empty.txt",
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
                                          FOLDER + "noWifi.txt",
                                          account.name);
         ocUpload.setUseWifiOnly(true);

+ 2 - 3
src/androidTest/java/com/owncloud/android/utils/FileUtilTest.kt

@@ -43,8 +43,7 @@ class FileUtilTest : AbstractIT() {
 
     @Test
     fun assertSlashInput() {
-        val tempPath =
-            File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + File.pathSeparator + "folder")
+        val tempPath = File(FileStorageUtils.getTemporalPath(account.name) + File.pathSeparator + "folder")
         if (!tempPath.exists()) {
             Assert.assertTrue(tempPath.mkdirs())
         }
@@ -59,7 +58,7 @@ class FileUtilTest : AbstractIT() {
 
     @Test
     fun assertFolderInput() {
-        val tempPath = File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext))
+        val tempPath = File(FileStorageUtils.getTemporalPath(account.name))
         if (!tempPath.exists()) {
             Assert.assertTrue(tempPath.mkdirs())
         }

+ 3 - 6
src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt

@@ -195,10 +195,7 @@ class SyncedFolderUtilsTest : AbstractIT() {
         getDummyFile(THUMBNAILS_FOLDER + File.separatorChar + IMAGE_JPEG)
         getDummyFile(THUMBNAILS_FOLDER + File.separatorChar + IMAGE_BITMAP)
         val folder = SyncedFolder(
-            FileStorageUtils.getInternalTemporalPath(
-                account.name,
-                targetContext
-            ) + File.separatorChar + THUMBNAILS_FOLDER,
+            FileStorageUtils.getTemporalPath(account.name) + File.separatorChar + THUMBNAILS_FOLDER,
             "",
             true,
             false,
@@ -235,7 +232,7 @@ class SyncedFolderUtilsTest : AbstractIT() {
         fun setUp() {
             val tempPath =
                 File(
-                    FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + File.separatorChar +
+                    FileStorageUtils.getTemporalPath(account.name) + File.separatorChar +
                         THUMBNAILS_FOLDER
                 )
             if (!tempPath.exists()) {
@@ -260,7 +257,7 @@ class SyncedFolderUtilsTest : AbstractIT() {
         @AfterClass
         @JvmStatic
         fun tearDown() {
-            FileUtils.deleteDirectory(File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext)))
+            FileUtils.deleteDirectory(File(FileStorageUtils.getTemporalPath(account.name)))
         }
     }
 }

+ 0 - 8
src/debug/AndroidManifest.xml

@@ -4,14 +4,6 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
-    <!-- Allows for storing and retrieving screenshots -->
-    <uses-permission
-        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
-        android:maxSdkVersion="29"
-        tools:ignore="ScopedStorage" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
-
     <!-- Allows changing locales -->
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"
         tools:ignore="ProtectedPermissions" />

+ 4 - 1
src/main/AndroidManifest.xml

@@ -32,7 +32,10 @@
         android:name="android.permission.WRITE_EXTERNAL_STORAGE"
         android:maxSdkVersion="29"
         tools:ignore="ScopedStorage" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission
+        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.VIBRATE" />
 

+ 0 - 2
src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt

@@ -195,10 +195,8 @@ class AccountRemovalWork(
 
     private fun removeFiles(user: User, storageManager: FileDataStorageManager) {
         val tempDir = File(FileStorageUtils.getTemporalPath(user.accountName))
-        val internalTempDir = File(FileStorageUtils.getInternalTemporalPath(user.accountName, context))
         val saveDir = File(FileStorageUtils.getSavePath(user.accountName))
         FileStorageUtils.deleteRecursively(tempDir, storageManager)
-        FileStorageUtils.deleteRecursively(internalTempDir, storageManager)
         FileStorageUtils.deleteRecursively(saveDir, storageManager)
     }
 

+ 3 - 17
src/main/java/com/nextcloud/client/onboarding/OnboardingServiceImpl.kt

@@ -22,7 +22,6 @@ import android.app.Activity
 import android.content.Context
 import android.content.Intent
 import android.content.res.Resources
-import android.os.Build
 import com.nextcloud.client.account.CurrentAccountProvider
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.BuildConfig
@@ -38,7 +37,7 @@ internal class OnboardingServiceImpl constructor(
 ) : OnboardingService {
 
     private companion object {
-        const val ITEM_VERSION_CODE = 30185300
+        const val ITEM_VERSION_CODE = 99999999
     }
 
     private val notSeenYet: Boolean
@@ -47,21 +46,8 @@ internal class OnboardingServiceImpl constructor(
         }
 
     override val whatsNew: Array<FeatureItem>
-        get() = if (!isFirstRun && notSeenYet && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            arrayOf(
-                FeatureItem(
-                    R.drawable.folder_alert_outline, R.string.whats_new_storage_sdk30_title,
-                    R.string
-                        .whats_new_storage_sdk30_content,
-                    true, false
-                ),
-                FeatureItem(
-                    R.drawable.folder_alert_outline, R.string.whats_new_storage_sdk30_title,
-                    R.string
-                        .whats_new_storage_sdk30_content_page2,
-                    true, false
-                )
-            )
+        get() = if (!isFirstRun && notSeenYet) {
+            emptyArray()
         } else {
             emptyArray()
         }

+ 3 - 5
src/main/java/com/owncloud/android/operations/DownloadFileOperation.java

@@ -23,7 +23,6 @@ package com.owncloud.android.operations;
 
 import android.accounts.Account;
 import android.content.Context;
-import android.os.FileUtils;
 import android.text.TextUtils;
 import android.webkit.MimeTypeMap;
 
@@ -105,11 +104,11 @@ public class DownloadFileOperation extends RemoteOperation {
     }
 
     public String getTmpPath() {
-        return FileStorageUtils.getInternalTemporalPath(account.name, context) + file.getRemotePath();
+        return FileStorageUtils.getTemporalPath(account.name) + file.getRemotePath();
     }
 
     public String getTmpFolder() {
-        return FileStorageUtils.getInternalTemporalPath(account.name, context);
+        return FileStorageUtils.getTemporalPath(account.name);
     }
 
     public String getRemotePath() {
@@ -204,8 +203,7 @@ public class DownloadFileOperation extends RemoteOperation {
                     return new RemoteOperationResult(e);
                 }
             }
-
-            moved = FileStorageUtils.moveFile(tmpFile, newFile);
+            moved = tmpFile.renameTo(newFile);
             newFile.setLastModified(file.getModificationTimestamp());
             if (!moved) {
                 result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);

+ 2 - 5
src/main/java/com/owncloud/android/operations/RenameFileOperation.java

@@ -21,7 +21,6 @@
 
 package com.owncloud.android.operations;
 
-import android.content.Context;
 import android.text.TextUtils;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -49,7 +48,6 @@ public class RenameFileOperation extends SyncOperation {
     private OCFile file;
     private String remotePath;
     private String newName;
-    private Context context;
 
     /**
      * Constructor
@@ -57,12 +55,11 @@ public class RenameFileOperation extends SyncOperation {
      * @param remotePath RemotePath of the OCFile instance describing the remote file or folder to rename
      * @param newName    New name to set as the name of file.
      */
-    public RenameFileOperation(String remotePath, String newName, FileDataStorageManager storageManager, Context context) {
+    public RenameFileOperation(String remotePath, String newName, FileDataStorageManager storageManager) {
         super(storageManager);
 
         this.remotePath = remotePath;
         this.newName = newName;
-        this.context = context;
     }
 
     /**
@@ -171,7 +168,7 @@ public class RenameFileOperation extends SyncOperation {
             return false;
         }
         // create a test file
-        String tmpFolderName = FileStorageUtils.getInternalTemporalPath("", context);
+        String tmpFolderName = FileStorageUtils.getTemporalPath("");
         File testFile = new File(tmpFolderName + newName);
         File tmpFolder = testFile.getParentFile();
         if (! tmpFolder.mkdirs()) {

+ 2 - 3
src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -343,8 +343,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
         RemoteOperationResult result = new RenameFileOperation(document.getRemotePath(),
                                                                displayName,
-                                                               document.getStorageManager(),
-                                                               getNonNullContext())
+                                                               document.getStorageManager())
             .execute(document.getClient());
 
         if (!result.isSuccess()) {
@@ -509,7 +508,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         User user = targetFolder.getUser();
 
         // create dummy file
-        File tempDir = new File(FileStorageUtils.getInternalTemporalPath(user.getAccountName(), getNonNullContext()));
+        File tempDir = new File(FileStorageUtils.getTemporalPath(user.getAccountName()));
 
         if (!tempDir.exists() && !tempDir.mkdirs()) {
             throw new FileNotFoundException("Temp folder could not be created: " + tempDir.getAbsolutePath());

+ 1 - 1
src/main/java/com/owncloud/android/services/OperationsService.java

@@ -658,7 +658,7 @@ public class OperationsService extends Service {
                     case ACTION_RENAME:
                         remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
                         String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
-                        operation = new RenameFileOperation(remotePath, newName, fileDataStorageManager, getApplicationContext());
+                        operation = new RenameFileOperation(remotePath, newName, fileDataStorageManager);
                         break;
 
                     case ACTION_REMOVE:

+ 5 - 3
src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -39,6 +39,7 @@ import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.text.TextUtils;
+import android.widget.Toast;
 
 import com.google.android.material.snackbar.Snackbar;
 import com.nextcloud.client.account.UserAccountManager;
@@ -91,6 +92,7 @@ import com.owncloud.android.utils.ClipboardUtil;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.FilesSyncHelper;
+import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.theme.ThemeSnackbarUtils;
 import com.owncloud.android.utils.theme.ThemeToolbarUtils;
 
@@ -919,9 +921,9 @@ public abstract class FileActivity extends DrawerActivity
      */
     @Override
     public void onShareProcessClosed() {
-        Fragment fragment  = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
-        if (fragment!=null){
-            ((FileDetailFragment)fragment).showHideFragmentView(false);
+        Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+        if (fragment != null) {
+            ((FileDetailFragment) fragment).showHideFragmentView(false);
         }
     }
 }

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

@@ -48,9 +48,9 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.widget.Toast;
 
 import com.google.android.material.appbar.AppBarLayout;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.snackbar.Snackbar;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.appinfo.AppInfo;

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

@@ -225,7 +225,7 @@ public class RichDocumentsEditorWebView extends EditorWebView {
             return;
         }
 
-        File targetFile = new File(FileStorageUtils.getInternalTemporalPath(account.getName(), getBaseContext()) + "/print.pdf");
+        File targetFile = new File(FileStorageUtils.getTemporalPath(account.getName()) + "/print.pdf");
 
         new PrintAsyncTask(targetFile, url.toString(), new WeakReference<>(this)).execute();
     }

+ 1 - 1
src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java

@@ -162,7 +162,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
                     }
                 }
 
-                fullTempPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mAppContext) + currentRemotePath;
+                fullTempPath = FileStorageUtils.getTemporalPath(user.getAccountName()) + currentRemotePath;
                 inputStream = leakedContentResolver.openInputStream(currentUri);
                 File cacheFile = new File(fullTempPath);
                 File tempDir = cacheFile.getParentFile();

+ 1 - 2
src/main/java/com/owncloud/android/utils/FileStorageUtils.java

@@ -96,10 +96,9 @@ public final class FileStorageUtils {
 
     /**
      * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
-     * @deprecated use {@link #getInternalTemporalPath(String, Context)}, as this one is broken on sdk >= 30
      */
-    @Deprecated
     public static String getTemporalPath(String accountName) {
+        // FIXME broken in SDK 30
         return MainApp.getStoragePath()
                 + File.separator
                 + MainApp.getDataFolder()

+ 40 - 9
src/main/java/com/owncloud/android/utils/PermissionUtil.kt

@@ -24,10 +24,17 @@ package com.owncloud.android.utils
 import android.Manifest
 import android.app.Activity
 import android.content.Context
+import android.content.Intent
 import android.content.pm.PackageManager
+import android.net.Uri
 import android.os.Build
+import android.os.Environment
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AlertDialog
 import androidx.core.app.ActivityCompat
 import androidx.core.content.ContextCompat
+import com.owncloud.android.R
 
 /**
  * Created by scherzia on 29.12.2015.
@@ -40,6 +47,8 @@ object PermissionUtil {
     const val PERMISSIONS_READ_CALENDAR_AUTOMATIC = 6
     const val PERMISSIONS_WRITE_CALENDAR = 7
 
+    const val REQUEST_CODE_MANAGE_ALL_FILES = 19203
+
     /**
      * Wrapper method for ContextCompat.checkSelfPermission().
      * Determine whether *the app* has been granted a particular permission.
@@ -75,8 +84,8 @@ object PermissionUtil {
      */
     @JvmStatic
     fun getExternalStoragePermission(): String = when {
-        Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> Manifest.permission.WRITE_EXTERNAL_STORAGE
-        else -> Manifest.permission.READ_EXTERNAL_STORAGE
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Manifest.permission.MANAGE_EXTERNAL_STORAGE
+        else -> Manifest.permission.WRITE_EXTERNAL_STORAGE
     }
 
     /**
@@ -85,8 +94,10 @@ object PermissionUtil {
      * @return `true` if app has the permission, or `false` if not.
      */
     @JvmStatic
-    fun checkExternalStoragePermission(context: Context): Boolean =
-        ContextCompat.checkSelfPermission(context, getExternalStoragePermission()) == PackageManager.PERMISSION_GRANTED
+    fun checkExternalStoragePermission(context: Context): Boolean = when {
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Environment.isExternalStorageManager()
+        else -> checkSelfPermission(context, getExternalStoragePermission())
+    }
 
     /**
      * Request relevant external storage permission depending on SDK.
@@ -94,11 +105,31 @@ object PermissionUtil {
      * @param activity The target activity.
      */
     @JvmStatic
-    fun requestExternalStoragePermission(activity: Activity) {
-        ActivityCompat.requestPermissions(
-            activity, arrayOf(getExternalStoragePermission()),
-            PERMISSIONS_EXTERNAL_STORAGE
-        )
+    fun requestExternalStoragePermission(activity: Activity) = when {
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> requestManageFilesPermission(activity)
+        else -> {
+            ActivityCompat.requestPermissions(
+                activity, arrayOf(getExternalStoragePermission()),
+                PERMISSIONS_EXTERNAL_STORAGE
+            )
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    private fun requestManageFilesPermission(activity: Activity) {
+        AlertDialog.Builder(activity, R.style.Theme_ownCloud_Dialog)
+            .setTitle(R.string.file_management_permission)
+            .setMessage(R.string.file_management_permission_text)
+            .setCancelable(false)
+            .setPositiveButton(R.string.common_ok) { dialog, _ ->
+                val intent = Intent().apply {
+                    action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
+                    data = Uri.parse("package:${activity.applicationContext.packageName}")
+                }
+                activity.startActivityForResult(intent, REQUEST_CODE_MANAGE_ALL_FILES)
+                dialog.dismiss()
+            }
+            .show()
     }
 
     /**

+ 2 - 0
src/main/res/values/strings.xml

@@ -1005,4 +1005,6 @@
     <string name="whats_new_storage_sdk30_title">Changes in storage access from Android 11</string>
     <string name="whats_new_storage_sdk30_content">From Android 11 onwards, strict storage access control is enforced.\n\nAs a consequence, Nextcloud will no longer be able to write or delete files on external storage, or read files in private folders owned by other apps.</string>
     <string name="whats_new_storage_sdk30_content_page2">Please check your auto upload items and verify that Nextcloud still has read access to the local folder.\n\nAdditionally, other applications will not be able to read Nextcloud\'s downloaded files directly from the external storage.</string>
+    <string name="file_management_permission">Permissions needed</string>
+    <string name="file_management_permission_text">Nextcloud needs file management permissions to work properly. Please enable it in the following screen to continue.</string>
 </resources>