diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java index 8dae417aecc..ffa8e46a935 100644 --- a/src/com/android/settings/applications/PackageManagerWrapper.java +++ b/src/com/android/settings/applications/PackageManagerWrapper.java @@ -24,6 +24,7 @@ import android.content.pm.IPackageDeleteObserver; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; +import android.os.storage.VolumeInfo; import java.util.List; @@ -105,4 +106,8 @@ public interface PackageManagerWrapper { */ void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags, int userId); + /** + * Calls {@code PackageManager.getPrimaryStorageCurrentVolume} + */ + VolumeInfo getPrimaryStorageCurrentVolume(); } diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java index a0d824fb732..8349702b700 100644 --- a/src/com/android/settings/applications/PackageManagerWrapperImpl.java +++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java @@ -24,6 +24,7 @@ import android.content.pm.IPackageDeleteObserver; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; +import android.os.storage.VolumeInfo; import java.util.List; @@ -97,4 +98,9 @@ public class PackageManagerWrapperImpl implements PackageManagerWrapper { int userId) { mPm.deletePackageAsUser(packageName, observer, flags, userId); } + + @Override + public VolumeInfo getPrimaryStorageCurrentVolume() { + return mPm.getPrimaryStorageCurrentVolume(); + } } diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java b/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java new file mode 100644 index 00000000000..4724c3d8644 --- /dev/null +++ b/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuController.java @@ -0,0 +1,85 @@ +/* + * 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.os.storage.VolumeInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.settings.R; +import com.android.settings.applications.PackageManagerWrapper; +import com.android.settings.core.lifecycle.Lifecycle; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnCreateOptionsMenu; +import com.android.settings.core.lifecycle.events.OnOptionsItemSelected; +import com.android.settings.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 PackageManagerWrapper mPm; + + public PrivateVolumeOptionMenuController( + Context context, VolumeInfo volumeInfo, PackageManagerWrapper 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)); + } + } + + @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; + } +} diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 18393acba64..75c0e753b05 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -18,6 +18,7 @@ package com.android.settings.deviceinfo; import android.app.LoaderManager; import android.content.Context; +import android.content.Intent; import android.content.Loader; import android.os.Bundle; import android.os.UserHandle; @@ -25,11 +26,17 @@ import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.provider.SearchIndexableResource; +import android.support.annotation.VisibleForTesting; +import android.util.Log; import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.applications.PackageManagerWrapper; import com.android.settings.applications.PackageManagerWrapperImpl; import com.android.settings.applications.UserManagerWrapper; import com.android.settings.applications.UserManagerWrapperImpl; @@ -48,16 +55,19 @@ import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; public class StorageDashboardFragment extends DashboardFragment implements LoaderManager.LoaderCallbacks> { private static final String TAG = "StorageDashboardFrag"; private static final int STORAGE_JOB_ID = 0; + private static final int OPTIONS_MENU_MIGRATE_DATA = 100; private VolumeInfo mVolume; private StorageSummaryDonutPreferenceController mSummaryController; private StorageItemPreferenceController mPreferenceController; + private PrivateVolumeOptionMenuController mOptionMenuController; private List mSecondaryUsers; @Override @@ -101,6 +111,9 @@ public class StorageDashboardFragment extends DashboardFragment return; } + mOptionMenuController = new PrivateVolumeOptionMenuController( + context, mVolume, new PackageManagerWrapperImpl(context.getPackageManager())); + final long sharedDataSize = mVolume.getPath().getTotalSpace(); long totalSize = sm.getPrimaryStorageSize(); long systemSize = totalSize - sharedDataSize; @@ -161,10 +174,16 @@ public class StorageDashboardFragment extends DashboardFragment new AutomaticStorageManagementSwitchPreferenceController( context, mMetricsFeatureProvider, getFragmentManager()); getLifecycle().addObserver(asmController); + getLifecycle().addObserver(mOptionMenuController); controllers.add(asmController); return controllers; } + @VisibleForTesting + protected void setVolume(VolumeInfo info) { + mVolume = info; + } + /** * Updates the secondary user controller sizes. */ diff --git a/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java new file mode 100644 index 00000000000..9ac9eb49d18 --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/PrivateVolumeOptionMenuControllerTest.java @@ -0,0 +1,116 @@ +/* + * 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.Matchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.applications.PackageManagerWrapper; +import com.android.settings.testutils.FakeFeatureFactory; + +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.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PrivateVolumeOptionMenuControllerTest { + @Mock + private MenuItem mMigrateMenuItem; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock + private MenuInflater mMenuInflater; + @Mock + private PackageManagerWrapper 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(mPrimaryInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); + when(mMenu.findItem(anyInt())).thenReturn(mMigrateMenuItem); + when(mMigrateMenuItem.getItemId()).thenReturn(100); + + mController = new PrivateVolumeOptionMenuController( + RuntimeEnvironment.application, 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); + + 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 testMigrateDataGoesToMigrateWizard() { + when(mPm.getPrimaryStorageCurrentVolume()).thenReturn(mVolumeInfo); + + mController.onCreateOptionsMenu(mMenu, mMenuInflater); + mController.onPrepareOptionsMenu(mMenu); + + assertThat(mController.onOptionsItemSelected(mMigrateMenuItem)).isTrue(); + assertThat(ShadowApplication.getInstance() + .getNextStartedActivity().getComponent().getClassName()) + .isEqualTo(StorageWizardMigrateConfirm.class.getName()); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java index 9baeda38514..b3253f97910 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License */ + package com.android.settings.deviceinfo; -import android.os.Bundle; -import android.os.storage.DiskInfo; +import static com.google.common.truth.Truth.assertThat; + import android.os.storage.StorageManager; -import android.os.storage.VolumeInfo; import android.provider.SearchIndexableResource; import com.android.settings.SettingsRobolectricTestRunner; @@ -36,15 +36,9 @@ import org.robolectric.shadows.ShadowApplication; import java.util.List; -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.when; - @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class StorageDashboardFragmentTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private StorageManager mStorageManager;