Merge "Cache mechanism for Storage page" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-02-23 12:13:38 +00:00
committed by Android (Google) Code Review
7 changed files with 351 additions and 46 deletions

View File

@@ -47,6 +47,7 @@ import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchP
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.StorageCacheHelper;
import com.android.settings.deviceinfo.storage.StorageEntry;
import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController;
@@ -109,6 +110,8 @@ public class StorageDashboardFragment extends DashboardFragment
private boolean mIsWorkProfile;
private int mUserId;
private Preference mFreeUpSpacePreference;
private boolean mIsLoadedFromCache;
private StorageCacheHelper mStorageCacheHelper;
private final StorageEventListener mStorageEventListener = new StorageEventListener() {
@Override
@@ -239,15 +242,27 @@ public class StorageDashboardFragment extends DashboardFragment
mPreferenceController.setVolume(null);
return;
}
if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) {
StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
mPreferenceController.setUsedSize(cachedData.usedSize);
mPreferenceController.setTotalSize(cachedData.totalSize);
}
if (mSelectedStorageEntry.isPrivate()) {
mStorageInfo = null;
mAppsResult = null;
// Hide the loading spinner if there is cached data.
if (mStorageCacheHelper.hasCachedSizeInfo()) {
//TODO(b/220259287): apply cache mechanism to secondary user
mPreferenceController.onLoadFinished(mAppsResult, mUserId);
} else {
maybeSetLoading(isQuotaSupported());
// To prevent flicker, sets null volume to hide category preferences.
// onReceivedSizes will setVolume with the volume of selected storage.
mPreferenceController.setVolume(null);
}
// Stats data is only available on private volumes.
getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
getLoaderManager()
@@ -277,6 +292,16 @@ public class StorageDashboardFragment extends DashboardFragment
initializePreference();
initializeOptionsMenu(activity);
if (mStorageCacheHelper.hasCachedSizeInfo()) {
mIsLoadedFromCache = true;
mStorageEntries.clear();
mStorageEntries.addAll(
StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
refreshUi();
updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
setSecondaryUsersVisible(true);
}
}
private void initializePreference() {
@@ -291,6 +316,7 @@ public class StorageDashboardFragment extends DashboardFragment
mUserManager = context.getSystemService(UserManager.class);
mIsWorkProfile = false;
mUserId = UserHandle.myUserId();
mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId);
super.onAttach(context);
use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
@@ -323,9 +349,14 @@ public class StorageDashboardFragment extends DashboardFragment
public void onResume() {
super.onResume();
if (mIsLoadedFromCache) {
mIsLoadedFromCache = false;
} else {
mStorageEntries.clear();
mStorageEntries.addAll(StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
mStorageEntries.addAll(
StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
refreshUi();
}
mStorageManager.registerListener(mStorageEventListener);
}
@@ -333,6 +364,11 @@ public class StorageDashboardFragment extends DashboardFragment
public void onPause() {
super.onPause();
mStorageManager.unregisterListener(mStorageEventListener);
// Destroy the data loaders to prevent unnecessary data loading when switching back to the
// page.
getLoaderManager().destroyLoader(STORAGE_JOB_ID);
getLoaderManager().destroyLoader(ICON_JOB_ID);
getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID);
}
@Override
@@ -359,6 +395,8 @@ public class StorageDashboardFragment extends DashboardFragment
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
mPreferenceController.setUsedSize(privateUsedBytes);
mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
// Cache total size and used size
mStorageCacheHelper.cacheTotalSizeAndUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
final AbstractPreferenceController controller = mSecondaryUsers.get(i);
if (controller instanceof SecondaryUserController) {

View File

@@ -66,7 +66,7 @@ public class StorageItemPreference extends Preference {
return;
mProgressBar.setMax(PROGRESS_MAX);
mProgressBar.setProgress(mProgressPercent);
mProgressBar.setProgress(mProgressPercent, true /* animate */);
}
@Override

View File

@@ -183,6 +183,9 @@ public class SecondaryUserController extends AbstractPreferenceController implem
@Override
public void handleResult(SparseArray<StorageAsyncLoader.StorageResult> stats) {
if (stats == null) {
return;
}
final StorageAsyncLoader.StorageResult result = stats.get(getUser().id);
if (result != null) {
setSize(result.externalStats.totalBytes);

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2022 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.content.Context;
import android.content.SharedPreferences;
/**
* A utility class to cache and restore the storage size information.
*/
public class StorageCacheHelper {
private static final String SHARED_PREFERENCE_NAME = "StorageCache";
private static final String TOTAL_SIZE_KEY = "total_size_key";
private static final String USED_SIZE_KEY = "used_size_key";
private static final String IMAGES_SIZE_KEY = "images_size_key";
private static final String VIDEOS_SIZE_KEY = "videos_size_key";
private static final String AUDIO_SIZE_KEY = "audio_size_key";
private static final String APPS_SIZE_KEY = "apps_size_key";
private static final String GAMES_SIZE_KEY = "games_size_key";
private static final String DOCUMENTS_AND_OTHER_SIZE_KEY = "documents_and_other_size_key";
private static final String TRASH_SIZE_KEY = "trash_size_key";
private static final String SYSTEM_SIZE_KEY = "system_size_key";
private final SharedPreferences mSharedPreferences;
public StorageCacheHelper(Context context, int userId) {
String sharedPrefName = SHARED_PREFERENCE_NAME + userId;
mSharedPreferences = context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE);
}
/**
* Returns true if there's a cached size info.
*/
public boolean hasCachedSizeInfo() {
return mSharedPreferences.getAll().size() > 0;
}
/**
* Cache the size info
* @param data a data about the file size info.
*/
public void cacheSizeInfo(StorageCache data) {
mSharedPreferences
.edit()
.putLong(IMAGES_SIZE_KEY, data.imagesSize)
.putLong(VIDEOS_SIZE_KEY, data.videosSize)
.putLong(AUDIO_SIZE_KEY, data.audioSize)
.putLong(APPS_SIZE_KEY, data.allAppsExceptGamesSize)
.putLong(GAMES_SIZE_KEY, data.gamesSize)
.putLong(DOCUMENTS_AND_OTHER_SIZE_KEY, data.documentsAndOtherSize)
.putLong(TRASH_SIZE_KEY, data.trashSize)
.putLong(SYSTEM_SIZE_KEY, data.systemSize)
.apply();
}
/**
* Cache total size and used size
*/
public void cacheTotalSizeAndUsedSize(long totalSize, long usedSize) {
mSharedPreferences
.edit()
.putLong(TOTAL_SIZE_KEY, totalSize)
.putLong(USED_SIZE_KEY, usedSize)
.apply();
}
/**
* Returns a cached data about all file size information.
*/
public StorageCache retrieveCachedSize() {
StorageCache result = new StorageCache();
result.totalSize = mSharedPreferences.getLong(TOTAL_SIZE_KEY, 0);
result.usedSize = mSharedPreferences.getLong(USED_SIZE_KEY, 0);
result.imagesSize = mSharedPreferences.getLong(IMAGES_SIZE_KEY, 0);
result.videosSize = mSharedPreferences.getLong(VIDEOS_SIZE_KEY, 0);
result.audioSize = mSharedPreferences.getLong(AUDIO_SIZE_KEY, 0);
result.allAppsExceptGamesSize = mSharedPreferences.getLong(APPS_SIZE_KEY, 0);
result.gamesSize = mSharedPreferences.getLong(GAMES_SIZE_KEY, 0);
result.documentsAndOtherSize = mSharedPreferences.getLong(DOCUMENTS_AND_OTHER_SIZE_KEY, 0);
result.trashSize = mSharedPreferences.getLong(TRASH_SIZE_KEY, 0);
result.systemSize = mSharedPreferences.getLong(SYSTEM_SIZE_KEY, 0);
return result;
}
/**
* All the cached data about the file size information.
*/
public static class StorageCache {
public long totalSize;
public long usedSize;
public long gamesSize;
public long allAppsExceptGamesSize;
public long audioSize;
public long imagesSize;
public long videosSize;
public long documentsAndOtherSize;
public long trashSize;
public long systemSize;
}
}

View File

@@ -34,6 +34,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
@@ -135,7 +136,11 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle
private boolean mIsWorkProfile;
private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
private StorageCacheHelper mStorageCacheHelper;
// The mIsDocumentsPrefShown being used here is to prevent a flicker problem from displaying
// the Document entry.
private boolean mIsDocumentsPrefShown;
private boolean mIsPreferenceOrderedBySize;
public StorageItemPreferenceController(Context context, Fragment hostFragment,
VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile) {
@@ -148,6 +153,8 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle
mIsWorkProfile = isWorkProfile;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mUserId = getCurrentUserId();
mIsDocumentsPrefShown = isDocumentsPrefShown();
mStorageCacheHelper = new StorageCacheHelper(mContext, mUserId);
mImagesUri = Uri.parse(context.getResources()
.getString(R.string.config_images_storage_category_uri));
@@ -267,14 +274,17 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle
// If we don't have a shared volume for our internal storage (or the shared volume isn't
// mounted as readable for whatever reason), we should hide the File preference.
if (visible) {
final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
mDocumentsAndOtherPreference.setVisible(sharedVolume != null
&& sharedVolume.isMountedReadable());
mDocumentsAndOtherPreference.setVisible(mIsDocumentsPrefShown);
} else {
mDocumentsAndOtherPreference.setVisible(false);
}
}
private boolean isDocumentsPrefShown() {
VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
return sharedVolume != null && sharedVolume.isMountedReadable();
}
private void updatePrivateStorageCategoryPreferencesOrder() {
if (mScreen == null || !isValidPrivateVolume()) {
return;
@@ -360,19 +370,54 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle
mTrashPreference = screen.findPreference(TRASH_KEY);
}
/** Fragments use it to set storage result and update UI of this controller. */
public void onLoadFinished(SparseArray<StorageAsyncLoader.StorageResult> result, int userId) {
final StorageAsyncLoader.StorageResult data = result.get(userId);
mImagesPreference.setStorageSize(data.imagesSize, mTotalSize);
mVideosPreference.setStorageSize(data.videosSize, mTotalSize);
mAudioPreference.setStorageSize(data.audioSize, mTotalSize);
mAppsPreference.setStorageSize(data.allAppsExceptGamesSize, mTotalSize);
mGamesPreference.setStorageSize(data.gamesSize, mTotalSize);
mDocumentsAndOtherPreference.setStorageSize(data.documentsAndOtherSize, mTotalSize);
mTrashPreference.setStorageSize(data.trashSize, mTotalSize);
/**
* Fragments use it to set storage result and update UI of this controller.
* @param result The StorageResult from StorageAsyncLoader. This allows a nullable result.
* When it's null, the cached storage size info will be used instead.
* @param userId User ID to get the storage size info
*/
public void onLoadFinished(@Nullable SparseArray<StorageAsyncLoader.StorageResult> result,
int userId) {
// Calculate the size info for each category
StorageCacheHelper.StorageCache storageCache = getSizeInfo(result, userId);
// Set size info to each preference
mImagesPreference.setStorageSize(storageCache.imagesSize, mTotalSize);
mVideosPreference.setStorageSize(storageCache.videosSize, mTotalSize);
mAudioPreference.setStorageSize(storageCache.audioSize, mTotalSize);
mAppsPreference.setStorageSize(storageCache.allAppsExceptGamesSize, mTotalSize);
mGamesPreference.setStorageSize(storageCache.gamesSize, mTotalSize);
mDocumentsAndOtherPreference.setStorageSize(storageCache.documentsAndOtherSize, mTotalSize);
mTrashPreference.setStorageSize(storageCache.trashSize, mTotalSize);
if (mSystemPreference != null) {
mSystemPreference.setStorageSize(storageCache.systemSize, mTotalSize);
}
// Cache the size info
if (result != null) {
mStorageCacheHelper.cacheSizeInfo(storageCache);
}
// Sort the preference according to size info in descending order
if (!mIsPreferenceOrderedBySize) {
updatePrivateStorageCategoryPreferencesOrder();
mIsPreferenceOrderedBySize = true;
}
setPrivateStorageCategoryPreferencesVisibility(true);
}
private StorageCacheHelper.StorageCache getSizeInfo(
SparseArray<StorageAsyncLoader.StorageResult> result, int userId) {
if (result == null) {
return mStorageCacheHelper.retrieveCachedSize();
}
StorageAsyncLoader.StorageResult data = result.get(userId);
StorageCacheHelper.StorageCache storageCache = new StorageCacheHelper.StorageCache();
storageCache.imagesSize = data.imagesSize;
storageCache.videosSize = data.videosSize;
storageCache.audioSize = data.audioSize;
storageCache.allAppsExceptGamesSize = data.allAppsExceptGamesSize;
storageCache.gamesSize = data.gamesSize;
storageCache.documentsAndOtherSize = data.documentsAndOtherSize;
storageCache.trashSize = data.trashSize;
// Everything else that hasn't already been attributed is tracked as
// belonging to system.
long attributedSize = 0;
@@ -388,14 +433,9 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle
+ otherData.allAppsExceptGamesSize;
attributedSize -= otherData.duplicateCodeSize;
}
final long systemSize = Math.max(DataUnit.GIBIBYTES.toBytes(1),
storageCache.systemSize = Math.max(DataUnit.GIBIBYTES.toBytes(1),
mUsedBytes - attributedSize);
mSystemPreference.setStorageSize(systemSize, mTotalSize);
}
updatePrivateStorageCategoryPreferencesOrder();
setPrivateStorageCategoryPreferencesVisibility(true);
return storageCache;
}
public void setUsedSize(long usedSizeBytes) {

View File

@@ -18,6 +18,7 @@ package com.android.settings.deviceinfo.storage;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -47,11 +48,13 @@ public class StorageUsageProgressBarPreferenceController extends BasePreferenceC
private UsageProgressBarPreference mUsageProgressBarPreference;
private StorageEntry mStorageEntry;
boolean mIsUpdateStateFromSelectedStorageEntry;
private StorageCacheHelper mStorageCacheHelper;
public StorageUsageProgressBarPreferenceController(Context context, String key) {
super(context, key);
mStorageStatsManager = context.getSystemService(StorageStatsManager.class);
mStorageCacheHelper = new StorageCacheHelper(context, UserHandle.myUserId());
}
/** Set StorageEntry to display. */
@@ -71,6 +74,15 @@ public class StorageUsageProgressBarPreferenceController extends BasePreferenceC
}
private void getStorageStatsAndUpdateUi() {
// Use cached data for both total size and used size.
if (mStorageEntry != null && mStorageEntry.isMounted() && mStorageEntry.isPrivate()) {
StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
mTotalBytes = cachedData.totalSize;
mUsedBytes = cachedData.usedSize;
mIsUpdateStateFromSelectedStorageEntry = true;
updateState(mUsageProgressBarPreference);
}
// Get the latest data from StorageStatsManager.
ThreadUtils.postOnBackgroundThread(() -> {
try {
if (mStorageEntry == null || !mStorageEntry.isMounted()) {

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.UserHandle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class StorageCacheHelperTest {
private static final long FAKE_IMAGES_SIZE = 7000L;
private static final long FAKE_VIDEOS_SIZE = 8900L;
private static final long FAKE_AUDIO_SIZE = 3500L;
private static final long FAKE_APPS_SIZE = 4000L;
private static final long FAKE_GAMES_SIZE = 5000L;
private static final long FAKE_DOCS_SIZE = 1500L;
private static final long FAKE_TRASH_SIZE = 500L;
private static final long FAKE_SYSTEM_SIZE = 2300L;
private static final long FAKE_TOTAL_SIZE = 256000L;
private static final long FAKE_USED_SIZE = 50000L;
private Context mContext;
private StorageCacheHelper mHelper;
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mHelper = new StorageCacheHelper(mContext, UserHandle.myUserId());
}
@Test
public void hasCachedSizeInfo_noCacheData_shouldReturnFalse() {
assertThat(mHelper.hasCachedSizeInfo()).isFalse();
}
@Test
public void hasCachedSizeInfo_hasCacheData_shouldReturnTrue() {
mHelper.cacheSizeInfo(getFakeStorageCache());
assertThat(mHelper.hasCachedSizeInfo()).isTrue();
}
@Test
public void cacheSizeInfo_shouldSaveToSharedPreference() {
mHelper.cacheSizeInfo(getFakeStorageCache());
StorageCacheHelper.StorageCache storageCache = mHelper.retrieveCachedSize();
assertThat(storageCache.imagesSize).isEqualTo(FAKE_IMAGES_SIZE);
assertThat(storageCache.totalSize).isEqualTo(0);
}
@Test
public void cacheTotalSizeAndUsedSize_shouldSaveToSharedPreference() {
mHelper.cacheTotalSizeAndUsedSize(FAKE_TOTAL_SIZE, FAKE_USED_SIZE);
StorageCacheHelper.StorageCache storageCache = mHelper.retrieveCachedSize();
assertThat(storageCache.totalSize).isEqualTo(FAKE_TOTAL_SIZE);
assertThat(storageCache.usedSize).isEqualTo(FAKE_USED_SIZE);
}
private StorageCacheHelper.StorageCache getFakeStorageCache() {
StorageCacheHelper.StorageCache result = new StorageCacheHelper.StorageCache();
result.trashSize = FAKE_TRASH_SIZE;
result.systemSize = FAKE_SYSTEM_SIZE;
result.imagesSize = FAKE_IMAGES_SIZE;
result.documentsAndOtherSize = FAKE_DOCS_SIZE;
result.audioSize = FAKE_AUDIO_SIZE;
result.gamesSize = FAKE_GAMES_SIZE;
result.videosSize = FAKE_VIDEOS_SIZE;
result.allAppsExceptGamesSize = FAKE_APPS_SIZE;
return result;
}
}