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
diff --git a/res/menu/storage_volume.xml b/res/menu/storage_volume.xml
index bf9f985..87f7515 100644
--- a/res/menu/storage_volume.xml
+++ b/res/menu/storage_volume.xml
@@ -28,9 +28,21 @@
         android:id="@+id/storage_format"
         android:title="@string/storage_menu_format" />
     <item
+        android:id="@+id/storage_format_as_portable"
+        android:title="@string/storage_menu_format_public"
+        android:visible="false" />
+    <item
+        android:id="@+id/storage_format_as_internal"
+        android:title="@string/storage_menu_format_private"
+        android:visible="false" />
+    <item
         android:id="@+id/storage_migrate"
         android:title="@string/storage_menu_migrate" />
     <item
         android:id="@+id/storage_free"
         android:title="@string/storage_menu_free" />
+    <item
+        android:id="@+id/storage_forget"
+        android:title="@string/storage_menu_forget"
+        android:visible="false" />
 </menu>
diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java b/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java
deleted file mode 100644
index 549a02f..0000000
--- 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 c9d2c6d..7af4f0c 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.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 @@
     private CachedStorageValuesHelper mCachedStorageValuesHelper;
 
     private StorageItemPreferenceController mPreferenceController;
-    private PrivateVolumeOptionMenuController mOptionMenuController;
+    private VolumeOptionMenuController mOptionMenuController;
     private StorageSelectionPreferenceController mStorageSelectionController;
     private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController;
     private List<AbstractPreferenceController> mSecondaryUsers;
@@ -110,22 +106,31 @@
                 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 @@
 
         @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 @@
 
         @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 @@
         }
     }
 
-    // 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 @@
         mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);
         mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry);
 
-        mOptionMenuController.setVolumeInfo(mSelectedStorageEntry.getVolumeInfo());
+        mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry);
+        getActivity().invalidateOptionsMenu();
 
         mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
 
@@ -263,18 +302,11 @@
             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 @@
 
     @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 @@
                 .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 @@
             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 0000000..0932447
--- /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 0000000..1e6a98d
--- /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 f86fa79..f718116 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageEntry.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageEntry.java
@@ -106,7 +106,7 @@
         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 @@
         if (isVolumeInfo()) {
             return mVolumeInfo.hashCode();
         }
-        if (isUnsupportedDiskInfo()) {
+        if (isDiskInfoUnsupported()) {
             return mUnsupportedDiskInfo.hashCode();
         }
         return mMissingVolumeRecord.hashCode();
@@ -128,7 +128,7 @@
         if (isVolumeInfo()) {
             return mVolumeInfo.toString();
         }
-        if (isUnsupportedDiskInfo()) {
+        if (isDiskInfoUnsupported()) {
             return mUnsupportedDiskInfo.toString();
         }
         return mMissingVolumeRecord.toString();
@@ -164,10 +164,10 @@
             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 @@
     }
 
     /** 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 @@
                 || 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 @@
         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 @@
         if (isVolumeInfo()) {
             return mVolumeInfo.getId();
         }
-        if (isUnsupportedDiskInfo()) {
+        if (isDiskInfoUnsupported()) {
             return mUnsupportedDiskInfo.getId();
         }
         return mMissingVolumeRecord.getFsUuid();
@@ -253,7 +263,7 @@
         if (isVolumeInfo()) {
             return mVolumeInfo.getDiskId();
         }
-        if (isUnsupportedDiskInfo()) {
+        if (isDiskInfoUnsupported()) {
             return mUnsupportedDiskInfo.getId();
         }
         return null;
@@ -264,7 +274,7 @@
         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 0000000..c67fe33
--- /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 0000000..26bdec0
--- /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 ed7f16b..0000000
--- 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 0000000..314f8c2
--- /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 603d51e..cf1b6b2 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 @@
         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