Skip to content

Commit 90e9a4f

Browse files
Merge pull request #15977 from nextcloud/improve-storage-permission-ui-ux
improve storage permission ui ux
2 parents 00d57f2 + 7e49ba7 commit 90e9a4f

24 files changed

+606
-464
lines changed

app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,13 @@ class MediaFoldersDetectionWork constructor(
8282
contentResolver,
8383
1,
8484
null,
85-
true,
86-
viewThemeUtils
85+
true
8786
)
8887
val videoMediaFolders = MediaProvider.getVideoFolders(
8988
contentResolver,
9089
1,
9190
null,
92-
true,
93-
viewThemeUtils
91+
true
9492
)
9593
val imageMediaFolderPaths: MutableList<String> = ArrayList()
9694
val videoMediaFolderPaths: MutableList<String> = ArrayList()

app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ public final class AppPreferencesImpl implements AppPreferences {
100100

101101
private static final String PREF__PDF_ZOOM_TIP_SHOWN = "pdf_zoom_tip_shown";
102102
private static final String PREF__MEDIA_FOLDER_LAST_PATH = "media_folder_last_path";
103-
104103
private static final String PREF__STORAGE_PERMISSION_REQUESTED = "storage_permission_requested";
105104
private static final String PREF__IN_APP_REVIEW_DATA = "in_app_review_data";
106105

app/src/main/java/com/nextcloud/utils/extensions/ContextExtensions.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ import android.content.Context
1414
import android.content.ContextWrapper
1515
import android.content.Intent
1616
import android.content.IntentFilter
17+
import android.net.Uri
1718
import android.os.Build
1819
import android.os.Handler
1920
import android.os.Looper
21+
import android.provider.Settings
2022
import android.view.WindowInsets
2123
import android.view.WindowManager
2224
import android.widget.Toast
25+
import androidx.core.net.toUri
2326
import androidx.core.view.ViewCompat
2427
import androidx.core.view.WindowInsetsCompat
2528
import com.owncloud.android.R
@@ -69,3 +72,22 @@ fun Context.getActivity(): Activity? = when (this) {
6972
is ContextWrapper -> baseContext.getActivity()
7073
else -> null
7174
}
75+
76+
fun Activity.openMediaPermissions(requestCode: Int) {
77+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
78+
data = Uri.fromParts("package", packageName, null)
79+
}
80+
startActivityForResult(intent, requestCode)
81+
}
82+
83+
fun Activity.openAllFilesAccessSettings(requestCode: Int) {
84+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
85+
return
86+
}
87+
88+
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
89+
data = "package:$packageName".toUri()
90+
}
91+
92+
startActivityForResult(intent, requestCode)
93+
}

app/src/main/java/com/owncloud/android/MainApp.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ public static void initSyncOperations(
628628
updateAutoUploadEntries(clock);
629629

630630
if (getAppContext() != null) {
631-
if (PermissionUtil.checkExternalStoragePermission(getAppContext())) {
631+
if (PermissionUtil.checkStoragePermission(getAppContext())) {
632632
splitOutAutoUploadEntries(clock, viewThemeUtils);
633633
} else {
634634
preferences.setAutoUploadSplitEntriesEnabled(true);
@@ -904,13 +904,11 @@ private static void splitOutAutoUploadEntries(Clock clock,
904904
final List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver,
905905
1,
906906
null,
907-
true,
908-
viewThemeUtils);
907+
true);
909908
final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver,
910909
1,
911910
null,
912-
true,
913-
viewThemeUtils);
911+
true);
914912

915913
ArrayList<Long> idsToDelete = new ArrayList<>();
916914
List<SyncedFolder> syncedFolders = syncedFolderProvider.getSyncedFolders();

app/src/main/java/com/owncloud/android/datamodel/MediaProvider.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,13 @@ private MediaProvider() {
5959
public static List<MediaFolder> getImageFolders(ContentResolver contentResolver,
6060
int itemLimit,
6161
@Nullable final AppCompatActivity activity,
62-
boolean getWithoutActivity,
63-
final ViewThemeUtils viewThemeUtils) {
62+
boolean getWithoutActivity) {
6463
// check permissions
65-
checkPermissions(activity, viewThemeUtils);
64+
checkPermissions(activity);
6665

6766
// query media/image folders
6867
Cursor cursorFolders = null;
69-
if (activity != null && PermissionUtil.checkExternalStoragePermission(activity.getApplicationContext())
68+
if (activity != null && PermissionUtil.checkStoragePermission(activity.getApplicationContext())
7069
|| getWithoutActivity) {
7170
cursorFolders = ContentResolverHelper.queryResolver(contentResolver, IMAGES_MEDIA_URI,
7271
IMAGES_FOLDER_PROJECTION, null, null,
@@ -159,25 +158,23 @@ private static boolean isValidAndExistingFilePath(String filePath) {
159158
return filePath != null && filePath.lastIndexOf('/') > 0 && new File(filePath).exists();
160159
}
161160

162-
private static void checkPermissions(@Nullable AppCompatActivity activity,
163-
final ViewThemeUtils viewThemeUtils) {
161+
private static void checkPermissions(@Nullable AppCompatActivity activity) {
164162
if (activity != null &&
165-
!PermissionUtil.checkExternalStoragePermission(activity.getApplicationContext())) {
166-
PermissionUtil.requestExternalStoragePermission(activity, viewThemeUtils, true);
163+
!PermissionUtil.checkStoragePermission(activity.getApplicationContext())) {
164+
PermissionUtil.requestStoragePermissionIfNeeded(activity);
167165
}
168166
}
169167

170168
public static List<MediaFolder> getVideoFolders(ContentResolver contentResolver,
171169
int itemLimit,
172170
@Nullable final AppCompatActivity activity,
173-
boolean getWithoutActivity,
174-
final ViewThemeUtils viewThemeUtils) {
171+
boolean getWithoutActivity) {
175172
// check permissions
176-
checkPermissions(activity, viewThemeUtils);
173+
checkPermissions(activity);
177174

178175
// query media/image folders
179176
Cursor cursorFolders = null;
180-
if ((activity != null && PermissionUtil.checkExternalStoragePermission(activity.getApplicationContext()))
177+
if ((activity != null && PermissionUtil.checkStoragePermission(activity.getApplicationContext()))
181178
|| getWithoutActivity) {
182179
cursorFolders = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, VIDEOS_FOLDER_PROJECTION,
183180
null, null, null);

app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.accounts.AuthenticatorException;
1616
import android.accounts.OperationCanceledException;
1717
import android.app.Activity;
18+
import android.app.ComponentCaller;
1819
import android.content.Context;
1920
import android.content.Intent;
2021
import android.content.res.ColorStateList;
@@ -52,7 +53,6 @@
5253
import com.nextcloud.client.account.User;
5354
import com.nextcloud.client.di.Injectable;
5455
import com.nextcloud.client.files.DeepLinkConstants;
55-
import com.nextcloud.client.jobs.upload.FileUploadWorker;
5656
import com.nextcloud.client.network.ClientFactory;
5757
import com.nextcloud.client.onboarding.FirstRunActivity;
5858
import com.nextcloud.client.preferences.AppPreferences;
@@ -145,6 +145,8 @@ public abstract class DrawerActivity extends ToolbarActivity
145145
private static final int MENU_ITEM_EXTERNAL_LINK = 111;
146146
private static final int MAX_LOGO_SIZE_PX = 1000;
147147
private static final int RELATIVE_THRESHOLD_WARNING = 80;
148+
public static final int REQ_ALL_FILES_ACCESS = 3001;
149+
public static final int REQ_MEDIA_ACCESS = 3000;
148150

149151
/**
150152
* Reference to the drawer layout.
@@ -1432,4 +1434,21 @@ public void showBottomNavigationBar(boolean show) {
14321434
public BottomNavigationView getBottomNavigationView() {
14331435
return bottomNavigationView;
14341436
}
1437+
1438+
@Override
1439+
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, @NonNull ComponentCaller caller) {
1440+
super.onActivityResult(requestCode, resultCode, data, caller);
1441+
1442+
if (requestCode == REQ_ALL_FILES_ACCESS || requestCode == REQ_MEDIA_ACCESS) {
1443+
checkStoragePermissionWarningBannerVisibility();
1444+
}
1445+
}
1446+
1447+
private void checkStoragePermissionWarningBannerVisibility() {
1448+
if (this instanceof SyncedFoldersActivity syncedFoldersActivity) {
1449+
syncedFoldersActivity.setupStoragePermissionWarningBanner();
1450+
} else if (this instanceof UploadFilesActivity uploadFilesActivity) {
1451+
uploadFilesActivity.setupStoragePermissionWarningBanner();
1452+
}
1453+
}
14351454
}

app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ import com.owncloud.android.utils.ErrorMessageAdapter
153153
import com.owncloud.android.utils.FileSortOrder
154154
import com.owncloud.android.utils.MimeTypeUtil
155155
import com.owncloud.android.utils.PermissionUtil
156-
import com.owncloud.android.utils.PermissionUtil.requestExternalStoragePermission
156+
import com.owncloud.android.utils.PermissionUtil.requestStoragePermissionIfNeeded
157157
import com.owncloud.android.utils.PermissionUtil.requestNotificationPermission
158158
import com.owncloud.android.utils.PushUtils
159159
import com.owncloud.android.utils.StringUtils
@@ -364,7 +364,7 @@ class FileDisplayActivity :
364364
if (dialog != null && dialog.isShowing) {
365365
dialog.dismiss()
366366
supportFragmentManager.beginTransaction().remove(fragment).commitNowAllowingStateLoss()
367-
requestExternalStoragePermission(this, viewThemeUtils)
367+
requestStoragePermissionIfNeeded(this)
368368
}
369369
}
370370
}
@@ -379,7 +379,7 @@ class FileDisplayActivity :
379379
// storage permissions handled in onRequestPermissionsResult
380380
requestNotificationPermission(this)
381381
} else {
382-
requestExternalStoragePermission(this, viewThemeUtils)
382+
requestStoragePermissionIfNeeded(this)
383383
}
384384

385385
if (intent.getParcelableArgument(
@@ -462,7 +462,7 @@ class FileDisplayActivity :
462462
// handle notification permission on API level >= 33
463463
PermissionUtil.PERMISSIONS_POST_NOTIFICATIONS ->
464464
// dialogue was dismissed -> prompt for storage permissions
465-
requestExternalStoragePermission(this, viewThemeUtils)
465+
requestStoragePermissionIfNeeded(this)
466466

467467
// If request is cancelled, result arrays are empty.
468468
PermissionUtil.PERMISSIONS_EXTERNAL_STORAGE ->

app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.nextcloud.client.preferences.AppPreferences;
5555
import com.nextcloud.client.preferences.AppPreferencesImpl;
5656
import com.nextcloud.client.preferences.DarkMode;
57+
import com.nextcloud.utils.extensions.ContextExtensionsKt;
5758
import com.nextcloud.utils.extensions.ViewExtensionsKt;
5859
import com.nextcloud.utils.mdm.MDMConfig;
5960
import com.owncloud.android.MainApp;
@@ -76,6 +77,7 @@
7677
import com.owncloud.android.utils.DisplayUtils;
7778
import com.owncloud.android.utils.EncryptionUtils;
7879
import com.owncloud.android.utils.MimeTypeUtil;
80+
import com.owncloud.android.utils.PermissionUtil;
7981
import com.owncloud.android.utils.theme.CapabilityUtils;
8082
import com.owncloud.android.utils.theme.ViewThemeUtils;
8183

@@ -93,6 +95,8 @@
9395
import androidx.core.content.ContextCompat;
9496
import androidx.core.content.res.ResourcesCompat;
9597

98+
import static com.owncloud.android.ui.activity.DrawerActivity.REQ_ALL_FILES_ACCESS;
99+
96100
/**
97101
* An Activity that allows the user to change the application's settings.
98102
* It proxies the necessary calls via {@link androidx.appcompat.app.AppCompatDelegate} to be used with AppCompat.
@@ -374,6 +378,7 @@ private void setupSyncCategory() {
374378

375379
setupAutoUploadPreference(preferenceCategorySync);
376380
setupInternalTwoWaySyncPreference();
381+
setupAllFilesAccessPreference(preferenceCategorySync);
377382
}
378383

379384
private void setupMoreCategory() {
@@ -620,6 +625,23 @@ private void setupInternalTwoWaySyncPreference() {
620625
});
621626
}
622627

628+
private void setupAllFilesAccessPreference(PreferenceCategory category) {
629+
Preference allFilesAccess = findPreference("allFilesAccess");
630+
631+
if (PermissionUtil.checkAllFilesAccess()) {
632+
category.removePreference(allFilesAccess);
633+
} else {
634+
if (allFilesAccess.getParent() == null) {
635+
category.addPreference(allFilesAccess);
636+
}
637+
}
638+
639+
allFilesAccess.setOnPreferenceClickListener(preference -> {
640+
ContextExtensionsKt.openAllFilesAccessSettings(this, REQ_ALL_FILES_ACCESS);
641+
return true;
642+
});
643+
}
644+
623645
private void setupBackupPreference() {
624646
Preference pContactsBackup = findPreference("backup");
625647
if (pContactsBackup != null) {
@@ -1067,6 +1089,9 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
10671089
storageMigration.setStorageMigrationProgressListener(this);
10681090
storageMigration.migrate();
10691091
}
1092+
} else if (requestCode == REQ_ALL_FILES_ACCESS) {
1093+
final PreferenceCategory preferenceCategorySync = (PreferenceCategory) findPreference("sync");
1094+
setupAllFilesAccessPreference(preferenceCategorySync);
10701095
}
10711096
}
10721097

app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker
3838
import com.nextcloud.client.preferences.SubFolderRule
3939
import com.nextcloud.utils.extensions.getParcelableArgument
4040
import com.nextcloud.utils.extensions.isDialogFragmentReady
41+
import com.nextcloud.utils.extensions.setVisibleIf
4142
import com.owncloud.android.BuildConfig
4243
import com.owncloud.android.MainApp
4344
import com.owncloud.android.R
45+
import com.owncloud.android.databinding.StoragePermissionWarningBannerBinding
4446
import com.owncloud.android.databinding.SyncedFoldersLayoutBinding
4547
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
4648
import com.owncloud.android.datamodel.MediaFolder
@@ -53,6 +55,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider
5355
import com.owncloud.android.files.services.NameCollisionPolicy
5456
import com.owncloud.android.lib.common.utils.Log_OC
5557
import com.owncloud.android.ui.adapter.SyncedFolderAdapter
58+
import com.owncloud.android.ui.adapter.storagePermissionBanner.setup
5659
import com.owncloud.android.ui.decoration.MediaGridItemDecoration
5760
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment
5861
import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment
@@ -197,7 +200,15 @@ class SyncedFoldersActivity :
197200
setTheme(R.style.FallbackThemingTheme)
198201
}
199202
binding.emptyList.emptyListViewAction.setOnClickListener { showHiddenItems() }
200-
PermissionUtil.requestExternalStoragePermission(this, viewThemeUtils, true)
203+
setupStoragePermissionWarningBanner()
204+
}
205+
206+
fun setupStoragePermissionWarningBanner() {
207+
val storagePermissionWarningBanner = binding.storagePermissionWarningBanner.root
208+
StoragePermissionWarningBannerBinding.bind(storagePermissionWarningBanner).apply {
209+
setup(this@SyncedFoldersActivity, R.string.storage_permission_banner_auto_upload_text)
210+
}
211+
storagePermissionWarningBanner.setVisibleIf(!PermissionUtil.checkStoragePermission(this))
201212
}
202213

203214
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -274,16 +285,14 @@ class SyncedFoldersActivity :
274285
contentResolver,
275286
perFolderMediaItemLimit,
276287
this@SyncedFoldersActivity,
277-
false,
278-
viewThemeUtils
288+
false
279289
)
280290
mediaFolders.addAll(
281291
MediaProvider.getVideoFolders(
282292
contentResolver,
283293
perFolderMediaItemLimit,
284294
this@SyncedFoldersActivity,
285-
false,
286-
viewThemeUtils
295+
false
287296
)
288297
)
289298

@@ -519,7 +528,7 @@ class SyncedFoldersActivity :
519528
android.R.id.home -> finish()
520529
R.id.action_create_custom_folder -> {
521530
Log_OC.d(TAG, "Show custom folder dialog")
522-
if (PermissionUtil.checkExternalStoragePermission(this)) {
531+
if (PermissionUtil.checkStoragePermission(this)) {
523532
val emptyCustomFolder = SyncedFolderDisplayItem(
524533
SyncedFolder.UNPERSISTED_ID,
525534
null,
@@ -542,7 +551,7 @@ class SyncedFoldersActivity :
542551
)
543552
onSyncFolderSettingsClick(0, emptyCustomFolder)
544553
} else {
545-
PermissionUtil.requestExternalStoragePermission(this, viewThemeUtils, true)
554+
PermissionUtil.requestStoragePermissionIfNeeded(this)
546555
}
547556
result = super.onOptionsItemSelected(item)
548557
}

0 commit comments

Comments
 (0)