diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index abefce6bb8e..efcf47907cc 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -81,6 +81,8 @@ public class StorageDashboardFragment extends DashboardFragment { final long usedBytes = mTotalSize - mVolume.getPath().getFreeSpace(); mSummaryController.updateBytes(usedBytes, mTotalSize); mPreferenceController.setVolume(mVolume); + mPreferenceController.setSystemSize(systemSize); + mPreferenceController.startMeasurement(); // Initialize the footer preference to go to the smart storage management. final FooterPreference pref = mFooterPreferenceMixin.createFooterPreference(); @@ -119,8 +121,8 @@ public class StorageDashboardFragment extends DashboardFragment { controllers.add(mSummaryController); StorageManager sm = context.getSystemService(StorageManager.class); - mPreferenceController = new StorageItemPreferenceController(context, this, mVolume, - new StorageManagerVolumeProvider(sm)); + mPreferenceController = new StorageItemPreferenceController(context, getLifecycle(), this, + mVolume, new StorageManagerVolumeProvider(sm)); controllers.add(mPreferenceController); controllers.add(new ManageStoragePreferenceController(context)); return controllers; diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index 3e802fd5cd4..dce8a25cd71 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -21,11 +21,14 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.VolumeInfo; import android.provider.DocumentsContract; +import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; import android.util.Log; import com.android.settings.R; @@ -33,24 +36,56 @@ import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.applications.ManageApplications; import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.Lifecycle; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnDestroy; +import com.android.settings.deviceinfo.StorageItemPreference; +import com.android.settingslib.deviceinfo.StorageMeasurement; import com.android.settingslib.deviceinfo.StorageVolumeProvider; +import java.util.HashMap; + /** * StorageItemPreferenceController handles the storage line items which summarize the storage * categorization breakdown. */ -public class StorageItemPreferenceController extends PreferenceController { +public class StorageItemPreferenceController extends PreferenceController + implements StorageMeasurement.MeasurementReceiver, LifecycleObserver, OnDestroy { private static final String TAG = "StorageItemPreference"; + + @VisibleForTesting + static final String PHOTO_KEY = "pref_photos_videos"; + @VisibleForTesting + static final String AUDIO_KEY = "pref_music_audio"; + @VisibleForTesting + static final String GAME_KEY = "pref_games"; + @VisibleForTesting + static final String OTHER_APPS_KEY = "pref_other_apps"; + @VisibleForTesting + static final String SYSTEM_KEY = "pref_system"; + @VisibleForTesting + static final String FILES_KEY = "pref_files"; + private final Fragment mFragment; private final StorageVolumeProvider mSvp; private VolumeInfo mVolume; private final int mUserId; + private StorageMeasurement mMeasure; + private long mSystemSize; + private long mUsedSize; + + private StorageItemPreferenceAlternate mPhotoPreference; + private StorageItemPreferenceAlternate mAudioPreference; + private StorageItemPreferenceAlternate mGamePreference; + private StorageItemPreferenceAlternate mAppPreference; + private StorageItemPreferenceAlternate mFilePreference; + private StorageItemPreferenceAlternate mSystemPreference; private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents"; - public StorageItemPreferenceController(Context context, Fragment hostFragment, - VolumeInfo volume, StorageVolumeProvider svp) { + public StorageItemPreferenceController(Context context, Lifecycle lifecycle, + Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp) { super(context); mFragment = hostFragment; mVolume = volume; @@ -58,6 +93,10 @@ public class StorageItemPreferenceController extends PreferenceController { UserManager um = mContext.getSystemService(UserManager.class); mUserId = um.getUserHandle(); + + if (lifecycle != null) { + lifecycle.addObserver(this); + } } @Override @@ -75,15 +114,15 @@ public class StorageItemPreferenceController extends PreferenceController { // After the intermediate views are built, swap them in. Intent intent = null; switch (preference.getKey()) { - case "pref_photos_videos": + case PHOTO_KEY: intent = getPhotosIntent(); break; - case "pref_music_audio": + case AUDIO_KEY: intent = getAudioIntent(); break; - case "pref_games": + case GAME_KEY: // TODO: Once app categorization is added, make this section. - case "pref_other_apps": + case OTHER_APPS_KEY: // Because we are likely constructed with a null volume, this is theoretically // possible. if (mVolume == null) { @@ -91,7 +130,7 @@ public class StorageItemPreferenceController extends PreferenceController { } intent = getAppsIntent(); break; - case "pref_files": + case FILES_KEY: intent = getFilesIntent(); break; } @@ -118,6 +157,81 @@ public class StorageItemPreferenceController extends PreferenceController { mVolume = volume; } + @Override + public void onDetailsChanged(StorageMeasurement.MeasurementDetails details) { + final long imagesSize = totalValues(details, mUserId, + Environment.DIRECTORY_DCIM, + Environment.DIRECTORY_PICTURES, + Environment.DIRECTORY_MOVIES); + if (mPhotoPreference != null) { + mPhotoPreference.setStorageSize(imagesSize); + } + + final long audioSize = totalValues(details, mUserId, + Environment.DIRECTORY_MUSIC, + Environment.DIRECTORY_ALARMS, + Environment.DIRECTORY_NOTIFICATIONS, + Environment.DIRECTORY_RINGTONES, + Environment.DIRECTORY_PODCASTS); + if (mAudioPreference != null) { + mAudioPreference.setStorageSize(audioSize); + } + + if (mGamePreference != null) { + mGamePreference.setStorageSize(0); + } + + final long appSize = details.appsSize.get(mUserId); + if (mAppPreference != null) { + mAppPreference.setStorageSize(appSize); + } + + if (mSystemPreference != null) { + mSystemPreference.setStorageSize(mSystemSize); + } + + final long downloadsSize = totalValues(details, mUserId, Environment.DIRECTORY_DOWNLOADS); + final long miscSize = details.miscSize.get(mUserId); + if (mFilePreference != null) { + mFilePreference.setStorageSize(downloadsSize + miscSize); + } + } + + @Override + public void onDestroy() { + if (mMeasure != null) { + mMeasure.onDestroy(); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mPhotoPreference = (StorageItemPreferenceAlternate) screen.findPreference(PHOTO_KEY); + mAudioPreference = (StorageItemPreferenceAlternate) screen.findPreference(AUDIO_KEY); + mGamePreference = (StorageItemPreferenceAlternate) screen.findPreference(GAME_KEY); + mAppPreference = (StorageItemPreferenceAlternate) screen.findPreference(OTHER_APPS_KEY); + mSystemPreference = (StorageItemPreferenceAlternate) screen.findPreference(SYSTEM_KEY); + mFilePreference = (StorageItemPreferenceAlternate) screen.findPreference(FILES_KEY); + } + + /** + * Begins an asynchronous storage measurement task for the preferences. + */ + public void startMeasurement() { + //TODO: When the GID-based measurement system is completed, swap in the GID impl. + mMeasure = new StorageMeasurement(mContext, mVolume, mSvp.findEmulatedForPrivate(mVolume)); + mMeasure.setReceiver(this); + mMeasure.forceMeasure(); + } + + /** + * Sets the system size for the system size preference. + * @param systemSize the size of the system in bytes + */ + public void setSystemSize(long systemSize) { + mSystemSize = systemSize; + } + private Intent getPhotosIntent() { Intent intent = new Intent(DocumentsContract.ACTION_BROWSE); intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root")); @@ -160,4 +274,20 @@ public class StorageItemPreferenceController extends PreferenceController { Log.w(TAG, "No activity found for " + intent); } } + + private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId, + String... keys) { + long total = 0; + HashMap map = details.mediaSize.get(userId); + if (map != null) { + for (String key : keys) { + if (map.containsKey(key)) { + total += map.get(key); + } + } + } else { + Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId); + } + return total; + } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java index 19936c468ff..b0c0b441c53 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -18,16 +18,20 @@ package com.android.settings.deviceinfo.storage; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; import android.content.Context; import android.content.Intent; +import android.os.Environment; import android.os.UserHandle; import android.os.storage.VolumeInfo; import android.provider.DocumentsContract; +import android.support.v7.preference.PreferenceScreen; import android.view.LayoutInflater; +import android.view.View; import android.widget.LinearLayout; import com.android.settings.R; @@ -36,6 +40,7 @@ import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SubSettings; import com.android.settings.TestConfig; import com.android.settings.applications.ManageApplications; +import com.android.settingslib.deviceinfo.StorageMeasurement; import com.android.settingslib.deviceinfo.StorageVolumeProvider; import org.junit.Before; @@ -44,13 +49,22 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.HashMap; + @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class StorageItemPreferenceControllerTest { + /** + * In O, this will change to 1000 instead of 1024 due to the formatter properly defining a + * kilobyte. + */ + private static long KILOBYTE = 1024L; + private Context mContext; private VolumeInfo mVolume; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -65,12 +79,15 @@ public class StorageItemPreferenceControllerTest { MockitoAnnotations.initMocks(this); mVolume = new VolumeInfo("id", 0, null, "id"); mContext = RuntimeEnvironment.application; - mController = new StorageItemPreferenceController(mContext, mFragment, mVolume, mSvp); + // Note: null is passed as the Lifecycle because we are handling it outside of the normal + // Settings fragment lifecycle for test purposes. + mController = new StorageItemPreferenceController(mContext, null, mFragment, mVolume, mSvp); mPreference = new StorageItemPreferenceAlternate(mContext); // Inflate the preference and the widget. LayoutInflater inflater = LayoutInflater.from(mContext); - inflater.inflate(mPreference.getLayoutResource(), new LinearLayout(mContext), false); + final View view = inflater.inflate( + mPreference.getLayoutResource(), new LinearLayout(mContext), false); } @Test @@ -142,4 +159,46 @@ public class StorageItemPreferenceControllerTest { assertThat(intent.getAction()).isEqualTo(browseIntent.getAction()); assertThat(intent.getData()).isEqualTo(browseIntent.getData()); } + + @Test + public void testMeasurementCompletedUpdatesPreferences() { + StorageItemPreferenceAlternate audio = new StorageItemPreferenceAlternate(mContext); + StorageItemPreferenceAlternate image = new StorageItemPreferenceAlternate(mContext); + StorageItemPreferenceAlternate games = new StorageItemPreferenceAlternate(mContext); + StorageItemPreferenceAlternate apps = new StorageItemPreferenceAlternate(mContext); + StorageItemPreferenceAlternate system = new StorageItemPreferenceAlternate(mContext); + StorageItemPreferenceAlternate files = new StorageItemPreferenceAlternate(mContext); + PreferenceScreen screen = mock(PreferenceScreen.class); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.AUDIO_KEY))).thenReturn(audio); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.PHOTO_KEY))).thenReturn(image); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.GAME_KEY))).thenReturn(games); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.OTHER_APPS_KEY))).thenReturn(apps); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.SYSTEM_KEY))).thenReturn(system); + when(screen.findPreference( + Mockito.eq(StorageItemPreferenceController.FILES_KEY))).thenReturn(files); + mController.displayPreference(screen); + + StorageMeasurement.MeasurementDetails details = new StorageMeasurement.MeasurementDetails(); + details.appsSize.put(0, KILOBYTE); + HashMap mediaSizes = new HashMap<>(); + mediaSizes.put(Environment.DIRECTORY_PICTURES, KILOBYTE * 2); + mediaSizes.put(Environment.DIRECTORY_MOVIES, KILOBYTE * 3); + mediaSizes.put(Environment.DIRECTORY_MUSIC, KILOBYTE * 4); + mediaSizes.put(Environment.DIRECTORY_DOWNLOADS, KILOBYTE * 5); + details.mediaSize.put(0, mediaSizes); + mController.setSystemSize(KILOBYTE * 6); + mController.onDetailsChanged(details); + + assertThat(audio.getSummary().toString()).isEqualTo("4.00KB"); + assertThat(image.getSummary().toString()).isEqualTo("5.00KB"); + assertThat(games.getSummary().toString()).isEqualTo("0"); + assertThat(apps.getSummary().toString()).isEqualTo("1.00KB"); + assertThat(system.getSummary().toString()).isEqualTo("6.00KB"); + assertThat(files.getSummary().toString()).isEqualTo("5.00KB"); + } } \ No newline at end of file