From c22749530b02f86c166eea73b64753aedd35abf2 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 1 Mar 2021 14:54:32 +0800 Subject: [PATCH] Add option menu in StorageDashboardFragment Add option menu for each kind of storage 1. Unsupported disk: Format. 2. Missing volume record: Forget. 3. Unmounted storage: Mount. 4. Default internal storage: Migrate. 5. Private volume: Rename / Unmount / Format as portable / Migrate. 6. Publuc volume: Rename / Unmount / Format / Format as internal. Bug: 174964885 Test: atest VolumeOptionMenuControllerTest Change-Id: I85fa117ff0a49ec7a53ba36580591c7ce7f5a8dc Merged-In: I85fa117ff0a49ec7a53ba36580591c7ce7f5a8dc --- res/menu/storage_volume.xml | 12 + .../PrivateVolumeOptionMenuController.java | 89 ------ .../deviceinfo/StorageDashboardFragment.java | 154 +++++------ .../VolumeOptionMenuController.java | 257 ++++++++++++++++++ .../deviceinfo/storage/DiskInitFragment.java | 75 +++++ .../deviceinfo/storage/StorageEntry.java | 32 ++- .../storage/StorageRenameFragment.java | 80 ++++++ .../deviceinfo/storage/StorageUtils.java | 47 ++++ ...PrivateVolumeOptionMenuControllerTest.java | 128 --------- .../VolumeOptionMenuControllerTest.java | 224 +++++++++++++++ .../deviceinfo/storage/StorageEntryTest.java | 16 +- 11 files changed, 791 insertions(+), 323 deletions(-) delete mode 100644 src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java create mode 100644 src/com/android/settings/deviceinfo/VolumeOptionMenuController.java create mode 100644 src/com/android/settings/deviceinfo/storage/DiskInitFragment.java create mode 100644 src/com/android/settings/deviceinfo/storage/StorageRenameFragment.java create mode 100644 src/com/android/settings/deviceinfo/storage/StorageUtils.java delete mode 100644 tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java create mode 100644 tests/unit/src/com/android/settings/deviceinfo/VolumeOptionMenuControllerTest.java diff --git a/res/menu/storage_volume.xml b/res/menu/storage_volume.xml index bf9f985aa63..87f75152ce3 100644 --- a/res/menu/storage_volume.xml +++ b/res/menu/storage_volume.xml @@ -27,10 +27,22 @@ + + + diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java b/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java deleted file mode 100644 index 549a02f18c4..00000000000 --- a/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.deviceinfo; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.storage.VolumeInfo; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import com.android.settings.R; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu; -import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected; -import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu; - -import java.util.Objects; - -/** - * Handles the option menu on the Storage settings. - */ -public class PrivateVolumeOptionMenuController implements LifecycleObserver, OnCreateOptionsMenu, - OnPrepareOptionsMenu, OnOptionsItemSelected { - private static final int OPTIONS_MENU_MIGRATE_DATA = 100; - - private Context mContext; - private VolumeInfo mVolumeInfo; - private PackageManager mPm; - - public PrivateVolumeOptionMenuController( - Context context, VolumeInfo volumeInfo, PackageManager packageManager) { - mContext = context; - mVolumeInfo = volumeInfo; - mPm = packageManager; - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - menu.add(Menu.NONE, OPTIONS_MENU_MIGRATE_DATA, 0, R.string.storage_menu_migrate); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - if (mVolumeInfo == null) { - return; - } - - // Only offer to migrate when not current storage - final VolumeInfo privateVol = mPm.getPrimaryStorageCurrentVolume(); - final MenuItem migrate = menu.findItem(OPTIONS_MENU_MIGRATE_DATA); - if (migrate != null) { - migrate.setVisible((privateVol != null) - && (privateVol.getType() == VolumeInfo.TYPE_PRIVATE) - && !Objects.equals(mVolumeInfo, privateVol) - && privateVol.isMountedWritable()); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == OPTIONS_MENU_MIGRATE_DATA) { - final Intent intent = new Intent(mContext, StorageWizardMigrateConfirm.class); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolumeInfo.getId()); - mContext.startActivity(intent); - return true; - } - return false; - } - - public void setVolumeInfo(VolumeInfo volumeInfo) { - mVolumeInfo = volumeInfo; - } -} diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index c9d2c6d5083..7af4f0cfd02 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -17,11 +17,9 @@ package com.android.settings.deviceinfo; import android.app.Activity; -import android.app.Dialog; import android.app.settings.SettingsEnums; import android.app.usage.StorageStatsManager; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -38,26 +36,24 @@ import android.util.SparseArray; import android.view.View; import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.SubSettingLauncher; -import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController; import com.android.settings.deviceinfo.storage.CachedStorageValuesHelper; +import com.android.settings.deviceinfo.storage.DiskInitFragment; import com.android.settings.deviceinfo.storage.SecondaryUserController; import com.android.settings.deviceinfo.storage.StorageAsyncLoader; import com.android.settings.deviceinfo.storage.StorageEntry; import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController; import com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController; +import com.android.settings.deviceinfo.storage.StorageUtils; import com.android.settings.deviceinfo.storage.UserIconLoader; import com.android.settings.deviceinfo.storage.VolumeSizesLoader; import com.android.settings.overlay.FeatureFactory; @@ -96,7 +92,7 @@ public class StorageDashboardFragment extends DashboardFragment private CachedStorageValuesHelper mCachedStorageValuesHelper; private StorageItemPreferenceController mPreferenceController; - private PrivateVolumeOptionMenuController mOptionMenuController; + private VolumeOptionMenuController mOptionMenuController; private StorageSelectionPreferenceController mStorageSelectionController; private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController; private List mSecondaryUsers; @@ -110,22 +106,31 @@ public class StorageDashboardFragment extends DashboardFragment return; } - final StorageEntry storageEntry = new StorageEntry(getContext(), volumeInfo); + final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo); switch (volumeInfo.getState()) { case VolumeInfo.STATE_MOUNTED: case VolumeInfo.STATE_MOUNTED_READ_ONLY: case VolumeInfo.STATE_UNMOUNTABLE: - if (!mStorageEntries.contains(storageEntry)) { - mStorageEntries.add(storageEntry); - refreshUi(); + // Add mounted or unmountable storage in the list and show it on spinner. + // Unmountable storages are the storages which has a problem format and android + // is not able to mount it automatically. + // Users can format an unmountable storage by the UI and then use the storage. + mStorageEntries.removeIf(storageEntry -> { + return storageEntry.equals(changedStorageEntry); + }); + mStorageEntries.add(changedStorageEntry); + if (changedStorageEntry.equals(mSelectedStorageEntry)) { + mSelectedStorageEntry = changedStorageEntry; } + refreshUi(); break; case VolumeInfo.STATE_REMOVED: case VolumeInfo.STATE_UNMOUNTED: case VolumeInfo.STATE_BAD_REMOVAL: case VolumeInfo.STATE_EJECTING: - if (mStorageEntries.remove(storageEntry)) { - if (mSelectedStorageEntry.equals(storageEntry)) { + // Remove removed storage from list and don't show it on spinner. + if (mStorageEntries.remove(changedStorageEntry)) { + if (changedStorageEntry.equals(mSelectedStorageEntry)) { mSelectedStorageEntry = StorageEntry.getDefaultInternalStorageEntry(getContext()); } @@ -139,10 +144,32 @@ public class StorageDashboardFragment extends DashboardFragment @Override public void onVolumeRecordChanged(VolumeRecord volumeRecord) { - final StorageEntry storageEntry = new StorageEntry(volumeRecord); - if (!mStorageEntries.contains(storageEntry)) { - mStorageEntries.add(storageEntry); - refreshUi(); + if (isVolumeRecordMissed(volumeRecord)) { + // VolumeRecord is a metadata of VolumeInfo, if a VolumeInfo is missing + // (e.g., internal SD card is removed.) show the missing storage to users, + // users can insert the SD card or manually forget the storage from the device. + final StorageEntry storageEntry = new StorageEntry(volumeRecord); + if (!mStorageEntries.contains(storageEntry)) { + mStorageEntries.add(storageEntry); + refreshUi(); + } + } else { + // Find mapped VolumeInfo and replace with existing one for something changed. + // (e.g., Renamed.) + final VolumeInfo mappedVolumeInfo = + mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid()); + if (mappedVolumeInfo == null) { + return; + } + + final boolean removeMappedStorageEntry = mStorageEntries.removeIf(storageEntry -> + storageEntry.isVolumeInfo() + && TextUtils.equals(storageEntry.getFsUuid(), volumeRecord.getFsUuid()) + ); + if (removeMappedStorageEntry) { + mStorageEntries.add(new StorageEntry(getContext(), mappedVolumeInfo)); + refreshUi(); + } } } @@ -161,7 +188,7 @@ public class StorageDashboardFragment extends DashboardFragment @Override public void onDiskScanned(DiskInfo disk, int volumeCount) { - if (!isInteresting(disk)) { + if (!isDiskUnsupported(disk)) { return; } final StorageEntry storageEntry = new StorageEntry(disk); @@ -195,8 +222,19 @@ public class StorageDashboardFragment extends DashboardFragment } } - // Only interested in unsupported disk. - private static boolean isInteresting(DiskInfo disk) { + /** + * VolumeRecord is a metadata of VolumeInfo, this is the case where a VolumeInfo is missing. + * (e.g., internal SD card is removed.) + */ + private boolean isVolumeRecordMissed(VolumeRecord volumeRecord) { + return volumeRecord.getType() == VolumeInfo.TYPE_PRIVATE + && mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid()) == null; + } + + /** + * A unsupported disk is the disk of problem format, android is not able to mount automatically. + */ + private static boolean isDiskUnsupported(DiskInfo disk) { return disk.volumeCount == 0 && disk.size > 0; } @@ -205,7 +243,8 @@ public class StorageDashboardFragment extends DashboardFragment mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry); mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry); - mOptionMenuController.setVolumeInfo(mSelectedStorageEntry.getVolumeInfo()); + mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry); + getActivity().invalidateOptionsMenu(); mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); @@ -263,18 +302,11 @@ public class StorageDashboardFragment extends DashboardFragment mSelectedStorageEntry = storageEntry; refreshUi(); - if (storageEntry.isUnsupportedDiskInfo() || storageEntry.isUnmountable()) { + if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) { DiskInitFragment.show(this, R.string.storage_dialog_unmountable, storageEntry.getDiskId()); - } else if (storageEntry.isMissingVolumeRecord()) { - final Bundle args = new Bundle(); - args.putString(VolumeRecord.EXTRA_FS_UUID, storageEntry.getFsUuid()); - new SubSettingLauncher(getContext()) - .setDestination(PrivateVolumeForget.class.getCanonicalName()) - .setTitleRes(R.string.storage_menu_forget) - .setSourceMetricsCategory(getMetricsCategory()) - .setArguments(args) - .launch(); + } else if (storageEntry.isVolumeRecordMissed()) { + StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry); } }); mStorageUsageProgressBarController = use(StorageUsageProgressBarPreferenceController.class); @@ -282,9 +314,8 @@ public class StorageDashboardFragment extends DashboardFragment @VisibleForTesting void initializeOptionsMenu(Activity activity) { - mOptionMenuController = new PrivateVolumeOptionMenuController(activity, - mSelectedStorageEntry.getVolumeInfo(), - activity.getPackageManager()); + mOptionMenuController = new VolumeOptionMenuController(activity, this, + mSelectedStorageEntry); getSettingsLifecycle().addObserver(mOptionMenuController); setHasOptionsMenu(true); activity.invalidateOptionsMenu(); @@ -312,15 +343,12 @@ public class StorageDashboardFragment extends DashboardFragment .filter(volumeInfo -> isInteresting(volumeInfo)) .map(volumeInfo -> new StorageEntry(getContext(), volumeInfo)) .collect(Collectors.toList())); - // Shows unsupported disks to give a chance to init. mStorageEntries.addAll(mStorageManager.getDisks().stream() - .filter(disk -> isInteresting(disk)) + .filter(disk -> isDiskUnsupported(disk)) .map(disk -> new StorageEntry(disk)) .collect(Collectors.toList())); - // Shows missing private volumes. mStorageEntries.addAll(mStorageManager.getVolumeRecords().stream() - .filter(volumeRecord -> volumeRecord.getType() == VolumeInfo.TYPE_PRIVATE - && mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid()) == null) + .filter(volumeRecord -> isVolumeRecordMissed(volumeRecord)) .map(volumeRecord -> new StorageEntry(volumeRecord)) .collect(Collectors.toList())); refreshUi(); @@ -619,52 +647,4 @@ public class StorageDashboardFragment extends DashboardFragment onReceivedSizes(); } } - - /** A dialog which guides users to initialize a specified unsupported disk. */ - public static class DiskInitFragment extends InstrumentedDialogFragment { - - private static final String TAG_DISK_INIT = "disk_init"; - - @Override - public int getMetricsCategory() { - return SettingsEnums.DIALOG_VOLUME_INIT; - } - - /** Shows the dialo for the specified diskId from DiskInfo. */ - public static void show(Fragment parent, int resId, String diskId) { - final Bundle args = new Bundle(); - args.putInt(Intent.EXTRA_TEXT, resId); - args.putString(DiskInfo.EXTRA_DISK_ID, diskId); - - final DiskInitFragment dialog = new DiskInitFragment(); - dialog.setArguments(args); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_DISK_INIT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final StorageManager storageManager = context.getSystemService(StorageManager.class); - final int resId = getArguments().getInt(Intent.EXTRA_TEXT); - final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID); - final DiskInfo disk = storageManager.findDiskById(diskId); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription())); - - builder.setPositiveButton(R.string.storage_menu_set_up, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Intent intent = new Intent(context, StorageWizardInit.class); - intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); - startActivity(intent); - } - }); - builder.setNegativeButton(R.string.cancel, null); - - return builder.create(); - } - } } diff --git a/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java b/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java new file mode 100644 index 00000000000..0932447bf20 --- /dev/null +++ b/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo; + +import android.app.ActivityManager; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.UserManager; +import android.os.storage.DiskInfo; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.Fragment; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.deviceinfo.StorageSettings.MountTask; +import com.android.settings.deviceinfo.StorageSettings.UnmountTask; +import com.android.settings.deviceinfo.storage.StorageEntry; +import com.android.settings.deviceinfo.storage.StorageRenameFragment; +import com.android.settings.deviceinfo.storage.StorageUtils; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu; +import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected; +import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu; + +import java.util.Objects; + +/** + * Handles the option menu on the Storage settings. + */ +public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOptionsMenu, + OnPrepareOptionsMenu, OnOptionsItemSelected { + + @VisibleForTesting + MenuItem mRename; + @VisibleForTesting + MenuItem mMount; + @VisibleForTesting + MenuItem mUnmount; + @VisibleForTesting + MenuItem mFormat; + @VisibleForTesting + MenuItem mFormatAsPortable; + @VisibleForTesting + MenuItem mFormatAsInternal; + @VisibleForTesting + MenuItem mMigrate; + @VisibleForTesting + MenuItem mFree; + @VisibleForTesting + MenuItem mForget; + + private final Context mContext; + private final Fragment mFragment; + private final PackageManager mPackageManager; + private final StorageManager mStorageManager; + private StorageEntry mStorageEntry; + + public VolumeOptionMenuController(Context context, Fragment parent, StorageEntry storageEntry) { + mContext = context; + mFragment = parent; + mPackageManager = context.getPackageManager(); + mStorageManager = context.getSystemService(StorageManager.class); + mStorageEntry = storageEntry; + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.storage_volume, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + mRename = menu.findItem(R.id.storage_rename); + mMount = menu.findItem(R.id.storage_mount); + mUnmount = menu.findItem(R.id.storage_unmount); + mFormat = menu.findItem(R.id.storage_format); + mFormatAsPortable = menu.findItem(R.id.storage_format_as_portable); + mFormatAsInternal = menu.findItem(R.id.storage_format_as_internal); + mMigrate = menu.findItem(R.id.storage_migrate); + mFree = menu.findItem(R.id.storage_free); + mForget = menu.findItem(R.id.storage_forget); + + mRename.setVisible(false); + mMount.setVisible(false); + mUnmount.setVisible(false); + mFormat.setVisible(false); + mFormatAsPortable.setVisible(false); + mFormatAsInternal.setVisible(false); + mMigrate.setVisible(false); + mFree.setVisible(false); + mForget.setVisible(false); + + if (mStorageEntry.isDiskInfoUnsupported()) { + mFormat.setVisible(true); + return; + } + if (mStorageEntry.isVolumeRecordMissed()) { + mForget.setVisible(true); + return; + } + if (mStorageEntry.isUnmounted()) { + mMount.setVisible(true); + return; + } + if (!mStorageEntry.isMounted()) { + return; + } + + if (mStorageEntry.isPrivate()) { + if (!mStorageEntry.isDefaultInternalStorage()) { + mRename.setVisible(true); + mUnmount.setVisible(true); + mFormatAsPortable.setVisible(true); + } + + // Only offer to migrate when not current storage. + final VolumeInfo primaryVolumeInfo = mPackageManager.getPrimaryStorageCurrentVolume(); + final VolumeInfo selectedVolumeInfo = mStorageEntry.getVolumeInfo(); + mMigrate.setVisible(primaryVolumeInfo != null + && primaryVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE + && !Objects.equals(selectedVolumeInfo, primaryVolumeInfo) + && primaryVolumeInfo.isMountedWritable()); + return; + } + + if (mStorageEntry.isPublic()) { + mRename.setVisible(true); + mUnmount.setVisible(true); + mFormat.setVisible(true); + final DiskInfo diskInfo = mStorageManager.findDiskById(mStorageEntry.getDiskId()); + mFormatAsInternal.setVisible(diskInfo != null + && diskInfo.isAdoptable() + && UserManager.get(mContext).isAdminUser() + && !ActivityManager.isUserAMonkey()); + return; + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + if (!mFragment.isAdded()) { + return false; + } + + final int menuId = menuItem.getItemId(); + if (menuId == R.id.storage_mount) { + if (mStorageEntry.isUnmounted()) { + new MountTask(mFragment.getActivity(), mStorageEntry.getVolumeInfo()).execute(); + return true; + } + return false; + } + if (menuId == R.id.storage_unmount) { + if (mStorageEntry.isMounted()) { + if (mStorageEntry.isPublic()) { + new UnmountTask(mFragment.getActivity(), + mStorageEntry.getVolumeInfo()).execute(); + return true; + } + if (mStorageEntry.isPrivate() && !mStorageEntry.isDefaultInternalStorage()) { + final Bundle args = new Bundle(); + args.putString(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId()); + new SubSettingLauncher(mContext) + .setDestination(PrivateVolumeUnmount.class.getCanonicalName()) + .setTitleRes(R.string.storage_menu_unmount) + .setSourceMetricsCategory(SettingsEnums.DEVICEINFO_STORAGE) + .setArguments(args) + .launch(); + return true; + } + } + return false; + } + if (menuId == R.id.storage_rename) { + if ((mStorageEntry.isPrivate() && !mStorageEntry.isDefaultInternalStorage()) + || mStorageEntry.isPublic()) { + StorageRenameFragment.show(mFragment, mStorageEntry.getVolumeInfo()); + return true; + } + return false; + } + if (menuId == R.id.storage_format) { + if (mStorageEntry.isDiskInfoUnsupported() || mStorageEntry.isPublic()) { + StorageWizardFormatConfirm.showPublic(mFragment.getActivity(), + mStorageEntry.getDiskId()); + return true; + } + return false; + } + if (menuId == R.id.storage_format_as_portable) { + if (mStorageEntry.isPrivate()) { + final Bundle args = new Bundle(); + args.putString(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId()); + new SubSettingLauncher(mContext) + .setDestination(PrivateVolumeFormat.class.getCanonicalName()) + .setTitleRes(R.string.storage_menu_format) + .setSourceMetricsCategory(SettingsEnums.DEVICEINFO_STORAGE) + .setArguments(args) + .launch(); + return true; + } + return false; + } + if (menuId == R.id.storage_format_as_internal) { + if (mStorageEntry.isPublic()) { + StorageWizardFormatConfirm.showPrivate(mFragment.getActivity(), + mStorageEntry.getDiskId()); + return true; + } + return false; + } + if (menuId == R.id.storage_migrate) { + if (mStorageEntry.isPrivate()) { + final Intent intent = new Intent(mContext, StorageWizardMigrateConfirm.class); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId()); + mContext.startActivity(intent); + return true; + } + return false; + } + if (menuId == R.id.storage_forget) { + if (mStorageEntry.isVolumeRecordMissed()) { + StorageUtils.launchForgetMissingVolumeRecordFragment(mContext, mStorageEntry); + return true; + } + return false; + } + return false; + } + + public void setSelectedStorageEntry(StorageEntry storageEntry) { + mStorageEntry = storageEntry; + } +} diff --git a/src/com/android/settings/deviceinfo/storage/DiskInitFragment.java b/src/com/android/settings/deviceinfo/storage/DiskInitFragment.java new file mode 100644 index 00000000000..1e6a98d175f --- /dev/null +++ b/src/com/android/settings/deviceinfo/storage/DiskInitFragment.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo.storage; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.storage.DiskInfo; +import android.os.storage.StorageManager; +import android.text.TextUtils; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.deviceinfo.StorageWizardInit; + +/** A dialog which guides users to initialize a specified unsupported disk. */ +public class DiskInitFragment extends InstrumentedDialogFragment { + + private static final String TAG_DISK_INIT = "disk_init"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_VOLUME_INIT; + } + + /** Shows the dialog for the specified diskId from DiskInfo. */ + public static void show(Fragment parent, int resId, String diskId) { + final Bundle args = new Bundle(); + args.putInt(Intent.EXTRA_TEXT, resId); + args.putString(DiskInfo.EXTRA_DISK_ID, diskId); + + final DiskInitFragment dialog = new DiskInitFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_DISK_INIT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final StorageManager storageManager = context.getSystemService(StorageManager.class); + final int resId = getArguments().getInt(Intent.EXTRA_TEXT); + final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID); + final DiskInfo disk = storageManager.findDiskById(diskId); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + return builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription())) + .setPositiveButton(R.string.storage_menu_set_up, (dialog, which) -> { + final Intent intent = new Intent(context, StorageWizardInit.class); + intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); + startActivity(intent); }) + .setNegativeButton(R.string.cancel, null) + .create(); + } +} + diff --git a/src/com/android/settings/deviceinfo/storage/StorageEntry.java b/src/com/android/settings/deviceinfo/storage/StorageEntry.java index f86fa794d69..f71811602d5 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageEntry.java +++ b/src/com/android/settings/deviceinfo/storage/StorageEntry.java @@ -106,7 +106,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.equals(StorageEntry.mVolumeInfo); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.equals(StorageEntry.mUnsupportedDiskInfo); } return mMissingVolumeRecord.equals(StorageEntry.mMissingVolumeRecord); @@ -117,7 +117,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.hashCode(); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.hashCode(); } return mMissingVolumeRecord.hashCode(); @@ -128,7 +128,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.toString(); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.toString(); } return mMissingVolumeRecord.toString(); @@ -164,10 +164,10 @@ public class StorageEntry implements Comparable, Parcelable { return 1; } - if (!isMissingVolumeRecord() && other.isMissingVolumeRecord()) { + if (!isVolumeRecordMissed() && other.isVolumeRecordMissed()) { return -1; } - if (isMissingVolumeRecord() && !other.isMissingVolumeRecord()) { + if (isVolumeRecordMissed() && !other.isVolumeRecordMissed()) { return 1; } @@ -192,12 +192,12 @@ public class StorageEntry implements Comparable, Parcelable { } /** If it's an unsupported DiskInfo. */ - public boolean isUnsupportedDiskInfo() { + public boolean isDiskInfoUnsupported() { return mUnsupportedDiskInfo != null; } /** If it's a missing VolumeRecord. */ - public boolean isMissingVolumeRecord() { + public boolean isVolumeRecordMissed() { return mMissingVolumeRecord != null; } @@ -216,6 +216,11 @@ public class StorageEntry implements Comparable, Parcelable { || mVolumeInfo.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY); } + /** If it's an unmounted storage. */ + public boolean isUnmounted() { + return mVolumeInfo == null ? false : (mVolumeInfo.getState() == VolumeInfo.STATE_UNMOUNTED); + } + /** If it's an unmountable storage. */ public boolean isUnmountable() { return mVolumeInfo == null ? false : mVolumeInfo.getState() == VolumeInfo.STATE_UNMOUNTABLE; @@ -226,12 +231,17 @@ public class StorageEntry implements Comparable, Parcelable { return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE; } + /** If it's a public storage. */ + public boolean isPublic() { + return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_PUBLIC; + } + /** Returns description. */ public String getDescription() { if (isVolumeInfo()) { return mVolumeInfoDescription; } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.getDescription(); } return mMissingVolumeRecord.getNickname(); @@ -242,7 +252,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.getId(); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.getId(); } return mMissingVolumeRecord.getFsUuid(); @@ -253,7 +263,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.getDiskId(); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return mUnsupportedDiskInfo.getId(); } return null; @@ -264,7 +274,7 @@ public class StorageEntry implements Comparable, Parcelable { if (isVolumeInfo()) { return mVolumeInfo.getFsUuid(); } - if (isUnsupportedDiskInfo()) { + if (isDiskInfoUnsupported()) { return null; } return mMissingVolumeRecord.getFsUuid(); diff --git a/src/com/android/settings/deviceinfo/storage/StorageRenameFragment.java b/src/com/android/settings/deviceinfo/storage/StorageRenameFragment.java new file mode 100644 index 00000000000..c67fe338ffa --- /dev/null +++ b/src/com/android/settings/deviceinfo/storage/StorageRenameFragment.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo.storage; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * Dialog that allows editing of volume nickname. + */ +public class StorageRenameFragment extends InstrumentedDialogFragment { + private static final String TAG_RENAME = "rename"; + + /** Shows the rename dialog. */ + public static void show(Fragment parent, VolumeInfo vol) { + final StorageRenameFragment dialog = new StorageRenameFragment(); + dialog.setTargetFragment(parent, 0 /* requestCode */); + final Bundle args = new Bundle(); + args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid()); + dialog.setArguments(args); + dialog.show(parent.getFragmentManager(), TAG_RENAME); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_VOLUME_RENAME; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final StorageManager storageManager = context.getSystemService(StorageManager.class); + + final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID); + final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); + + final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false); + final EditText nickname = (EditText) view.findViewById(R.id.edittext); + nickname.setText(rec.getNickname()); + + return builder.setTitle(R.string.storage_rename_title) + .setView(view) + .setPositiveButton(R.string.save, (dialog, which) -> + // TODO: move to background thread + storageManager.setVolumeNickname(fsUuid, nickname.getText().toString())) + .setNegativeButton(R.string.cancel, null) + .create(); + } +} diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java new file mode 100644 index 00000000000..26bdec0556c --- /dev/null +++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo.storage; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.os.storage.VolumeRecord; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.deviceinfo.PrivateVolumeForget; + +/** Storage utilities */ +public class StorageUtils { + + /** Launches the fragment to forget a specified missing volume record. */ + public static void launchForgetMissingVolumeRecordFragment(Context context, + StorageEntry storageEntry) { + if (storageEntry == null || !storageEntry.isVolumeRecordMissed()) { + return; + } + + final Bundle args = new Bundle(); + args.putString(VolumeRecord.EXTRA_FS_UUID, storageEntry.getFsUuid()); + new SubSettingLauncher(context) + .setDestination(PrivateVolumeForget.class.getCanonicalName()) + .setTitleRes(R.string.storage_menu_forget) + .setSourceMetricsCategory(SettingsEnums.SETTINGS_STORAGE_CATEGORY) + .setArguments(args) + .launch(); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java deleted file mode 100644 index ed7f16b24db..00000000000 --- a/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.deviceinfo; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Activity; -import android.content.pm.PackageManager; -import android.os.storage.VolumeInfo; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import com.android.settings.R; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.shadows.ShadowApplication; - -@RunWith(RobolectricTestRunner.class) -public class PrivateVolumeOptionMenuControllerTest { - - @Mock - private MenuItem mMigrateMenuItem; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Menu mMenu; - @Mock - private MenuInflater mMenuInflater; - @Mock - private PackageManager mPm; - @Mock - private VolumeInfo mVolumeInfo; - @Mock - private VolumeInfo mPrimaryInfo; - - private PrivateVolumeOptionMenuController mController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); - when(mVolumeInfo.isMountedWritable()).thenReturn(true); - when(mPrimaryInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); - when(mMenu.findItem(anyInt())).thenReturn(mMigrateMenuItem); - when(mMigrateMenuItem.getItemId()).thenReturn(100); - - mController = new PrivateVolumeOptionMenuController( - Robolectric.setupActivity(Activity.class), mPrimaryInfo, mPm); - } - - @Test - public void testMigrateDataMenuItemIsAdded() { - mController.onCreateOptionsMenu(mMenu, mMenuInflater); - - verify(mMenu).add(Menu.NONE, 100, Menu.NONE, R.string.storage_menu_migrate); - } - - @Test - public void testMigrateDataIsNotVisibleNormally() { - when(mPm.getPrimaryStorageCurrentVolume()).thenReturn(mPrimaryInfo); - when(mPrimaryInfo.isMountedWritable()).thenReturn(true); - - mController.onCreateOptionsMenu(mMenu, mMenuInflater); - mController.onPrepareOptionsMenu(mMenu); - - verify(mMigrateMenuItem).setVisible(false); - } - - @Test - public void testMigrateDataIsVisibleWhenExternalVolumeIsPrimary() { - when(mPm.getPrimaryStorageCurrentVolume()).thenReturn(mVolumeInfo); - - mController.onCreateOptionsMenu(mMenu, mMenuInflater); - mController.onPrepareOptionsMenu(mMenu); - - verify(mMigrateMenuItem).setVisible(true); - } - - @Test - public void testMigrateDataIsNotVisibleWhenExternalVolumeIsNotMounted() { - when(mPm.getPrimaryStorageCurrentVolume()).thenReturn(mVolumeInfo); - when(mVolumeInfo.isMountedWritable()).thenReturn(false); - - mController.onCreateOptionsMenu(mMenu, mMenuInflater); - mController.onPrepareOptionsMenu(mMenu); - - verify(mMigrateMenuItem).setVisible(false); - } - - @Test - public void testMigrateDataGoesToMigrateWizard() { - when(mPm.getPrimaryStorageCurrentVolume()).thenReturn(mVolumeInfo); - - mController.onCreateOptionsMenu(mMenu, mMenuInflater); - mController.onPrepareOptionsMenu(mMenu); - - assertThat(mController.onOptionsItemSelected(mMigrateMenuItem)).isTrue(); - ShadowApplication shadowApplication = ShadowApplication.getInstance(); - assertThat(shadowApplication).isNotNull(); - assertThat(shadowApplication.getNextStartedActivity().getComponent().getClassName()) - .isEqualTo(StorageWizardMigrateConfirm.class.getName()); - } -} diff --git a/tests/unit/src/com/android/settings/deviceinfo/VolumeOptionMenuControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/VolumeOptionMenuControllerTest.java new file mode 100644 index 00000000000..314f8c22138 --- /dev/null +++ b/tests/unit/src/com/android/settings/deviceinfo/VolumeOptionMenuControllerTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.storage.DiskInfo; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; +import android.view.Menu; + +import androidx.fragment.app.Fragment; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.deviceinfo.storage.StorageEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class VolumeOptionMenuControllerTest { + + private static final String INTERNAL_VOLUME_ID = "1"; + private static final String EXTERNAL_VOLUME_ID = "2"; + private static final String DISK_ID = "3"; + private static final String VOLUME_RECORD_FSUUID = "volume_record_fsuuid"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock private PackageManager mPackageManager; + @Mock private StorageManager mStorageManager; + @Mock private VolumeInfo mExternalVolumeInfo; + @Mock private VolumeInfo mInternalVolumeInfo; + + private Context mContext; + private VolumeOptionMenuController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager); + + when(mInternalVolumeInfo.getId()).thenReturn(INTERNAL_VOLUME_ID); + when(mInternalVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); + when(mInternalVolumeInfo.getState()).thenReturn(VolumeInfo.STATE_MOUNTED); + when(mInternalVolumeInfo.isMountedWritable()).thenReturn(true); + when(mExternalVolumeInfo.getId()).thenReturn(EXTERNAL_VOLUME_ID); + + final StorageEntry selectedStorageEntry = new StorageEntry(mContext, mInternalVolumeInfo); + mController = new VolumeOptionMenuController(mContext, mock(Fragment.class), + selectedStorageEntry); + } + + @Test + public void onPrepareOptionsMenu_unSupportedDiskInfo_formatIsVisible() { + final StorageEntry unsupportedStorageEntry = + new StorageEntry(new DiskInfo(DISK_ID, 0 /* flags */)); + mController.setSelectedStorageEntry(unsupportedStorageEntry); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mFormat, atLeastOnce()).setVisible(true); + verify(mController.mRename, never()).setVisible(true); + verify(mController.mMount, never()).setVisible(true); + verify(mController.mUnmount, never()).setVisible(true); + verify(mController.mFormatAsPortable, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mMigrate, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + verify(mController.mForget, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_missingVolumeRecord_forgetIsVisible() { + final StorageEntry missingStorageEntry = + new StorageEntry(new VolumeRecord(0 /* type */, VOLUME_RECORD_FSUUID)); + mController.setSelectedStorageEntry(missingStorageEntry); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mForget, atLeastOnce()).setVisible(true); + verify(mController.mRename, never()).setVisible(true); + verify(mController.mMount, never()).setVisible(true); + verify(mController.mUnmount, never()).setVisible(true); + verify(mController.mFormat, never()).setVisible(true); + verify(mController.mFormatAsPortable, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mMigrate, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_unmountedStorage_mountIsVisible() { + when(mInternalVolumeInfo.getState()).thenReturn(VolumeInfo.STATE_UNMOUNTED); + mController.setSelectedStorageEntry(new StorageEntry(mContext, mInternalVolumeInfo)); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mMount, atLeastOnce()).setVisible(true); + verify(mController.mRename, never()).setVisible(true); + verify(mController.mUnmount, never()).setVisible(true); + verify(mController.mFormat, never()).setVisible(true); + verify(mController.mFormatAsPortable, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mMigrate, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + verify(mController.mForget, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_privateNotDefaultInternal_someMenusAreVisible() { + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mRename, atLeastOnce()).setVisible(true); + verify(mController.mUnmount, atLeastOnce()).setVisible(true); + verify(mController.mFormatAsPortable, atLeastOnce()).setVisible(true); + verify(mController.mMount, never()).setVisible(true); + verify(mController.mFormat, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + verify(mController.mForget, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_privateDefaultInternal_mostMenusAreNotVisible() { + when(mInternalVolumeInfo.getId()).thenReturn(VolumeInfo.ID_PRIVATE_INTERNAL); + when(mPackageManager.getPrimaryStorageCurrentVolume()).thenReturn(mInternalVolumeInfo); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mRename, never()).setVisible(true); + verify(mController.mUnmount, never()).setVisible(true); + verify(mController.mFormatAsPortable, never()).setVisible(true); + verify(mController.mMount, never()).setVisible(true); + verify(mController.mFormat, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + verify(mController.mForget, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_publicStorage_someMenusArcVisible() { + when(mExternalVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PUBLIC); + when(mExternalVolumeInfo.getState()).thenReturn(VolumeInfo.STATE_MOUNTED); + when(mExternalVolumeInfo.getDiskId()).thenReturn(DISK_ID); + final DiskInfo externalDiskInfo = mock(DiskInfo.class); + when(mStorageManager.findDiskById(DISK_ID)).thenReturn(externalDiskInfo); + mController.setSelectedStorageEntry(new StorageEntry(mContext, mExternalVolumeInfo)); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mRename, atLeastOnce()).setVisible(true); + verify(mController.mUnmount, atLeastOnce()).setVisible(true); + verify(mController.mFormat, atLeastOnce()).setVisible(true); + verify(mController.mMount, never()).setVisible(true); + verify(mController.mFormatAsPortable, never()).setVisible(true); + verify(mController.mFormatAsInternal, never()).setVisible(true); + verify(mController.mFree, never()).setVisible(true); + verify(mController.mForget, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_noExternalStorage_migrateNotVisible() { + when(mPackageManager.getPrimaryStorageCurrentVolume()).thenReturn(mInternalVolumeInfo); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mMigrate, atLeastOnce()).setVisible(false); + verify(mController.mMigrate, never()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_externalPrimaryStorageAvailable_migrateIsVisible() { + when(mExternalVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); + when(mExternalVolumeInfo.isMountedWritable()).thenReturn(true); + when(mPackageManager.getPrimaryStorageCurrentVolume()).thenReturn(mExternalVolumeInfo); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mMigrate, atLeastOnce()).setVisible(true); + } + + @Test + public void onPrepareOptionsMenu_externalUnmounted_migrateIsVisible() { + when(mExternalVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); + when(mExternalVolumeInfo.isMountedWritable()).thenReturn(false); + when(mPackageManager.getPrimaryStorageCurrentVolume()).thenReturn(mExternalVolumeInfo); + + mController.onPrepareOptionsMenu(mMenu); + + verify(mController.mMigrate, atLeastOnce()).setVisible(false); + verify(mController.mMigrate, never()).setVisible(true); + } +} diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java index 603d51e236f..cf1b6b2c8ec 100644 --- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java +++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java @@ -131,28 +131,28 @@ public class StorageEntryTest { final StorageEntry storage = new StorageEntry(mContext, volumeInfo); assertThat(storage.isVolumeInfo()).isTrue(); - assertThat(storage.isUnsupportedDiskInfo()).isFalse(); - assertThat(storage.isMissingVolumeRecord()).isFalse(); + assertThat(storage.isDiskInfoUnsupported()).isFalse(); + assertThat(storage.isVolumeRecordMissed()).isFalse(); } @Test - public void isUnsupportedDiskInfo_shouldReturnTrueForDiskInfo() { + public void isDiskInfoUnsupported_shouldReturnTrueForDiskInfo() { final DiskInfo diskInfo = mock(DiskInfo.class); final StorageEntry storage = new StorageEntry(diskInfo); assertThat(storage.isVolumeInfo()).isFalse(); - assertThat(storage.isUnsupportedDiskInfo()).isTrue(); - assertThat(storage.isMissingVolumeRecord()).isFalse(); + assertThat(storage.isDiskInfoUnsupported()).isTrue(); + assertThat(storage.isVolumeRecordMissed()).isFalse(); } @Test - public void isMissingVolumeRecord_shouldReturnTrueForVolumeRecord() { + public void isVolumeRecordMissed_shouldReturnTrueForVolumeRecord() { final VolumeRecord volumeRecord = mock(VolumeRecord.class); final StorageEntry storage = new StorageEntry(volumeRecord); assertThat(storage.isVolumeInfo()).isFalse(); - assertThat(storage.isUnsupportedDiskInfo()).isFalse(); - assertThat(storage.isMissingVolumeRecord()).isTrue(); + assertThat(storage.isDiskInfoUnsupported()).isFalse(); + assertThat(storage.isVolumeRecordMissed()).isTrue(); } @Test