Merge "Expose Lock screen settings page." into sc-dev
diff --git a/res/values/config.xml b/res/values/config.xml
index 21d9c75..1e2855b 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -511,8 +511,8 @@
<!-- Media Uri to view videos storage category. -->
<string name="config_videos_storage_category_uri" translatable="false">content://com.android.providers.media.documents/root/videos_root</string>
- <!-- Media Uri to view audios storage category. -->
- <string name="config_audios_storage_category_uri" translatable="false">content://com.android.providers.media.documents/root/audio_root</string>
+ <!-- Media Uri to view audio storage category. -->
+ <string name="config_audio_storage_category_uri" translatable="false">content://com.android.providers.media.documents/root/audio_root</string>
<!-- Media Uri to view documents & other storage category. -->
<string name="config_documents_and_other_storage_category_uri" translatable="false">content://com.android.providers.media.documents/root/documents_root</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a34f909..4f377ee 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11883,8 +11883,8 @@
<!-- Preference label for the Videos storage section. [CHAR LIMIT=50] -->
<string name="storage_videos">Videos</string>
- <!-- Preference label for the Audios storage section. [CHAR LIMIT=50] -->
- <string name="storage_audios">Audios</string>
+ <!-- Preference label for the Audio storage section. [CHAR LIMIT=50] -->
+ <string name="storage_audio">Audio</string>
<!-- Preference label for the Apps storage section. [CHAR LIMIT=50] -->
<string name="storage_apps">Apps</string>
diff --git a/res/xml/storage_category_fragment.xml b/res/xml/storage_category_fragment.xml
new file mode 100644
index 0000000..72623d7
--- /dev/null
+++ b/res/xml/storage_category_fragment.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout for the storage breakdown for a profile of the primary user. -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/storage_settings">
+ <Preference
+ android:key="free_up_space"
+ android:order="4"
+ android:title="@string/storage_free_up_space_title"
+ android:summary="@string/storage_free_up_space_summary"
+ android:icon="@drawable/ic_files_go_round"
+ settings:allowDividerAbove="true"/>
+ <!-- Preference order 100~200 are 'ONLY' for storage category preferences below. -->
+ <Preference
+ android:key="pref_public_storage"
+ android:title="@string/storage_files"
+ android:icon="@drawable/ic_folder_vd_theme_24"
+ android:order="100"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_images"
+ android:title="@string/storage_images"
+ android:icon="@drawable/ic_photo_library"
+ android:order="101"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_videos"
+ android:title="@string/storage_videos"
+ android:icon="@drawable/ic_local_movies"
+ android:order="102"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_audio"
+ android:title="@string/storage_audio"
+ android:icon="@drawable/ic_media_stream"
+ android:order="103"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_apps"
+ android:title="@string/storage_apps"
+ android:icon="@drawable/ic_storage_apps"
+ android:order="104"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_games"
+ android:title="@string/storage_games"
+ android:icon="@drawable/ic_videogame_vd_theme_24"
+ android:order="105"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_documents_and_other"
+ android:title="@string/storage_documents_and_other"
+ android:icon="@drawable/ic_folder_vd_theme_24"
+ android:order="106"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_system"
+ android:title="@string/storage_system"
+ android:icon="@drawable/ic_system_update"
+ android:order="107"/>
+ <com.android.settings.deviceinfo.StorageItemPreference
+ android:key="pref_trash"
+ android:title="@string/storage_trash"
+ android:icon="@drawable/ic_trash_can"
+ android:order="108"/>
+ <!-- Preference order 100~200 are 'ONLY' for storage category preferences above. -->
+ <PreferenceCategory
+ android:key="pref_secondary_users"
+ android:title="@string/storage_other_users"
+ android:order="201" />
+</PreferenceScreen>
diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml
index 71d0712..655e683 100644
--- a/res/xml/storage_dashboard_fragment.xml
+++ b/res/xml/storage_dashboard_fragment.xml
@@ -18,7 +18,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/storage_settings"
- android:orderingFromXml="false"
settings:keywords="@string/keywords_storage">
<com.android.settingslib.widget.SettingsSpinnerPreference
android:key="storage_spinner"
@@ -45,8 +44,8 @@
android:order="4"
android:title="@string/storage_free_up_space_title"
android:summary="@string/storage_free_up_space_summary"
- settings:allowDividerAbove="true"
- android:icon="@drawable/ic_files_go_round"/>
+ android:icon="@drawable/ic_files_go_round"
+ settings:allowDividerAbove="true"/>
<!-- Preference order 100~200 are 'ONLY' for storage category preferences below. -->
<Preference
android:key="pref_public_storage"
@@ -64,8 +63,8 @@
android:icon="@drawable/ic_local_movies"
android:order="102"/>
<com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_audios"
- android:title="@string/storage_audios"
+ android:key="pref_audio"
+ android:title="@string/storage_audio"
android:icon="@drawable/ic_media_stream"
android:order="103"/>
<com.android.settings.deviceinfo.StorageItemPreference
diff --git a/res/xml/storage_dashboard_header_fragment.xml b/res/xml/storage_dashboard_header_fragment.xml
new file mode 100644
index 0000000..dec6f89
--- /dev/null
+++ b/res/xml/storage_dashboard_header_fragment.xml
@@ -0,0 +1,42 @@
+<!--
+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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="storage_header"
+ android:title="@string/storage_settings">
+ <com.android.settingslib.widget.SettingsSpinnerPreference
+ android:key="storage_spinner"
+ android:order="1"
+ settings:searchable="false"
+ settings:controller="com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController"/>
+ <com.android.settingslib.widget.UsageProgressBarPreference
+ android:key="storage_summary"
+ android:order="2"
+ android:selectable="false"
+ settings:searchable="false"
+ settings:controller="com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController"
+ settings:allowDividerBelow="true"/>
+ <com.android.settings.widget.PrimarySwitchPreference
+ android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings"
+ android:key="toggle_asm"
+ android:title="@string/automatic_storage_manager_preference_title"
+ android:icon="@drawable/ic_storage"
+ android:order="3"
+ settings:allowDividerAbove="true"
+ settings:controller="com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/storage_profile_fragment.xml b/res/xml/storage_profile_fragment.xml
deleted file mode 100644
index a12bdd5..0000000
--- a/res/xml/storage_profile_fragment.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- Layout for the storage breakdown for a profile of the primary user. -->
-<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/storage_settings">
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_photos_videos"
- android:title="@string/storage_photos_videos"
- android:icon="@drawable/ic_photo_library"
- android:order="2" />
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_music_audio"
- android:title="@string/storage_music_audio"
- android:icon="@drawable/ic_media_stream"
- android:order="3" />
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_games"
- android:title="@string/storage_games"
- android:icon="@drawable/ic_videogame_vd_theme_24"
- android:order="4" />
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_movies"
- android:title="@string/storage_movies_tv"
- android:icon="@drawable/ic_local_movies"
- android:order="5"
- />
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_other_apps"
- android:title="@string/storage_other_apps"
- android:icon="@drawable/ic_storage_apps"
- android:order="6" />
- <com.android.settings.deviceinfo.StorageItemPreference
- android:key="pref_files"
- android:title="@string/storage_files"
- android:icon="@drawable/ic_folder_vd_theme_24"
- android:order="7" />
-</PreferenceScreen>
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
index 86ead5c..016ac05 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
@@ -45,6 +45,12 @@
}
@Override
+ public int getHelpResource() {
+ // Hides help center in action bar and footer bar in SuW
+ return 0;
+ }
+
+ @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java
index 9e505e3..c4ff91b 100644
--- a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java
+++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java
@@ -16,32 +16,301 @@
package com.android.settings.dashboard.profileselector;
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
import android.os.Bundle;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.text.TextUtils;
+import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
-import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.deviceinfo.StorageCategoryFragment;
+import com.android.settings.deviceinfo.VolumeOptionMenuController;
+import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController;
+import com.android.settings.deviceinfo.storage.DiskInitFragment;
+import com.android.settings.deviceinfo.storage.StorageEntry;
+import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController;
+import com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController;
+import com.android.settings.deviceinfo.storage.StorageUtils;
+
+import java.util.ArrayList;
+import java.util.List;
/**
- * Storage Settings page for personal/managed profile.
+ * Storage Settings main UI is composed by 3 fragments:
+ *
+ * StorageDashboardFragment only shows when there is only personal profile for current user.
+ *
+ * ProfileSelectStorageFragment (controls preferences above profile tab) and
+ * StorageCategoryFragment (controls preferences below profile tab) only show when current
+ * user has installed work profile.
+ *
+ * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same
+ * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to
+ * change Storage Settings.
*/
public class ProfileSelectStorageFragment extends ProfileSelectFragment {
+ private static final String TAG = "ProfileSelStorageFrag";
+ private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key";
+
+ private StorageManager mStorageManager;
+
+ private final List<StorageEntry> mStorageEntries = new ArrayList<>();
+ private StorageEntry mSelectedStorageEntry;
+ private Fragment[] mFragments;
+
+ private StorageSelectionPreferenceController mStorageSelectionController;
+ private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController;
+ private VolumeOptionMenuController mOptionMenuController;
+
+ private final StorageEventListener mStorageEventListener = new StorageEventListener() {
+ @Override
+ public void onVolumeStateChanged(VolumeInfo volumeInfo, int oldState, int newState) {
+ if (!StorageUtils.isStorageSettingsInterestedVolume(volumeInfo)) {
+ return;
+ }
+
+ final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo);
+ switch (volumeInfo.getState()) {
+ case VolumeInfo.STATE_MOUNTED:
+ case VolumeInfo.STATE_MOUNTED_READ_ONLY:
+ case VolumeInfo.STATE_UNMOUNTABLE:
+ // 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:
+ // Remove removed storage from list and don't show it on spinner.
+ if (mStorageEntries.remove(changedStorageEntry)) {
+ if (changedStorageEntry.equals(mSelectedStorageEntry)) {
+ mSelectedStorageEntry =
+ StorageEntry.getDefaultInternalStorageEntry(getContext());
+ }
+ refreshUi();
+ }
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ @Override
+ public void onVolumeRecordChanged(VolumeRecord volumeRecord) {
+ if (StorageUtils.isVolumeRecordMissed(mStorageManager, 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();
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeForgotten(String fsUuid) {
+ final StorageEntry storageEntry = new StorageEntry(
+ new VolumeRecord(VolumeInfo.TYPE_PUBLIC, fsUuid));
+ if (mStorageEntries.remove(storageEntry)) {
+ if (mSelectedStorageEntry.equals(storageEntry)) {
+ mSelectedStorageEntry =
+ StorageEntry.getDefaultInternalStorageEntry(getContext());
+ }
+ refreshUi();
+ }
+ }
+
+ @Override
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
+ if (!StorageUtils.isDiskUnsupported(disk)) {
+ return;
+ }
+ final StorageEntry storageEntry = new StorageEntry(disk);
+ if (!mStorageEntries.contains(storageEntry)) {
+ mStorageEntries.add(storageEntry);
+ refreshUi();
+ }
+ }
+
+ @Override
+ public void onDiskDestroyed(DiskInfo disk) {
+ final StorageEntry storageEntry = new StorageEntry(disk);
+ if (mStorageEntries.remove(storageEntry)) {
+ if (mSelectedStorageEntry.equals(storageEntry)) {
+ mSelectedStorageEntry =
+ StorageEntry.getDefaultInternalStorageEntry(getContext());
+ }
+ refreshUi();
+ }
+ }
+ };
+
@Override
public Fragment[] getFragments() {
+ if (mFragments != null) {
+ return mFragments;
+ }
+
final Bundle workBundle = new Bundle();
workBundle.putInt(EXTRA_PROFILE, ProfileType.WORK);
- final Fragment workFragment = new StorageDashboardFragment();
+ final Fragment workFragment = new StorageCategoryFragment();
workFragment.setArguments(workBundle);
final Bundle personalBundle = new Bundle();
personalBundle.putInt(EXTRA_PROFILE, ProfileType.PERSONAL);
- final Fragment personalFragment = new StorageDashboardFragment();
+ final Fragment personalFragment = new StorageCategoryFragment();
personalFragment.setArguments(personalBundle);
- return new Fragment[] {
+ mFragments = new Fragment[] {
personalFragment,
workFragment
};
+ return mFragments;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.storage_dashboard_header_fragment;
+ }
+
+ private void refreshUi() {
+ mStorageSelectionController.setStorageEntries(mStorageEntries);
+ mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);
+ mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry);
+
+ for (Fragment fragment : getFragments()) {
+ if (!(fragment instanceof StorageCategoryFragment)) {
+ throw new IllegalStateException("Wrong fragment type to refreshUi");
+ }
+ ((StorageCategoryFragment) fragment).refreshUi(mSelectedStorageEntry);
+ }
+
+ mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry);
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final Activity activity = getActivity();
+ mStorageManager = activity.getSystemService(StorageManager.class);
+
+ if (icicle == null) {
+ final VolumeInfo specifiedVolumeInfo =
+ Utils.maybeInitializeVolume(mStorageManager, getArguments());
+ mSelectedStorageEntry = specifiedVolumeInfo == null
+ ? StorageEntry.getDefaultInternalStorageEntry(getContext())
+ : new StorageEntry(getContext(), specifiedVolumeInfo);
+ } else {
+ mSelectedStorageEntry = icicle.getParcelable(SELECTED_STORAGE_ENTRY_KEY);
+ }
+
+ initializeOptionsMenu(activity);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
+ getFragmentManager());
+ mStorageSelectionController = use(StorageSelectionPreferenceController.class);
+ mStorageSelectionController.setOnItemSelectedListener(storageEntry -> {
+ mSelectedStorageEntry = storageEntry;
+ refreshUi();
+
+ if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) {
+ DiskInitFragment.show(this, R.string.storage_dialog_unmountable,
+ storageEntry.getDiskId());
+ } else if (storageEntry.isVolumeRecordMissed()) {
+ StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry);
+ }
+ });
+ mStorageUsageProgressBarController = use(StorageUsageProgressBarPreferenceController.class);
+ }
+
+ @VisibleForTesting
+ void initializeOptionsMenu(Activity activity) {
+ mOptionMenuController = new VolumeOptionMenuController(activity, this,
+ mSelectedStorageEntry);
+ getSettingsLifecycle().addObserver(mOptionMenuController);
+ setHasOptionsMenu(true);
+ activity.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ mStorageEntries.clear();
+ mStorageEntries.addAll(StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
+ refreshUi();
+ mStorageManager.registerListener(mStorageEventListener);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mStorageManager.unregisterListener(mStorageEventListener);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public int getHelpResource() {
+ return R.string.help_url_storage_dashboard;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_STORAGE_CATEGORY;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
}
}
diff --git a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java
new file mode 100644
index 0000000..ba59498
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java
@@ -0,0 +1,415 @@
+/*
+ * 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.settings.SettingsEnums;
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
+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.dashboard.DashboardFragment;
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
+import com.android.settings.deviceinfo.storage.CachedStorageValuesHelper;
+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.UserIconLoader;
+import com.android.settings.deviceinfo.storage.VolumeSizesLoader;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Storage Settings main UI is composed by 3 fragments:
+ *
+ * StorageDashboardFragment only shows when there is only personal profile for current user.
+ *
+ * ProfileSelectStorageFragment (controls preferences above profile tab) and
+ * StorageCategoryFragment (controls preferences below profile tab) only show when current
+ * user has installed work profile.
+ *
+ * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same
+ * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to
+ * change Storage Settings.
+ */
+public class StorageCategoryFragment extends DashboardFragment
+ implements
+ LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>>,
+ Preference.OnPreferenceClickListener {
+ private static final String TAG = "StorageCategoryFrag";
+ private static final String SUMMARY_PREF_KEY = "storage_summary";
+ private static final String FREE_UP_SPACE_PREF_KEY = "free_up_space";
+ private static final int STORAGE_JOB_ID = 0;
+ private static final int ICON_JOB_ID = 1;
+ private static final int VOLUME_SIZE_JOB_ID = 2;
+
+ private StorageManager mStorageManager;
+ private UserManager mUserManager;
+ private StorageEntry mSelectedStorageEntry;
+ private PrivateStorageInfo mStorageInfo;
+ private SparseArray<StorageAsyncLoader.AppsStorageResult> mAppsResult;
+ private CachedStorageValuesHelper mCachedStorageValuesHelper;
+
+ private StorageItemPreferenceController mPreferenceController;
+ private List<AbstractPreferenceController> mSecondaryUsers;
+ private boolean mIsWorkProfile;
+ private int mUserId;
+ private Preference mFreeUpSpacePreference;
+
+ /**
+ * Refresh UI for specified storageEntry.
+ */
+ public void refreshUi(StorageEntry storageEntry) {
+ mSelectedStorageEntry = storageEntry;
+ if (mPreferenceController == null) {
+ // Check null here because when onResume, StorageCategoryFragment may not
+ // onAttach to createPreferenceControllers and mPreferenceController will be null.
+ return;
+ }
+
+ mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
+
+ if (!mSelectedStorageEntry.isMounted()) {
+ // Set null volume to hide category stats.
+ mPreferenceController.setVolume(null);
+ return;
+ }
+ if (mSelectedStorageEntry.isPrivate()) {
+ // Stats data is only available on private volumes.
+ getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
+ getLoaderManager()
+ .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
+ getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
+ } else {
+ mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mStorageManager = getActivity().getSystemService(StorageManager.class);
+
+ initializePreference();
+ }
+
+ private void initializePreference() {
+ mFreeUpSpacePreference = getPreferenceScreen().findPreference(FREE_UP_SPACE_PREF_KEY);
+ mFreeUpSpacePreference.setOnPreferenceClickListener(this);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ // These member variables are initialized befoer super.onAttach for
+ // createPreferenceControllers to work correctly.
+ mUserManager = context.getSystemService(UserManager.class);
+ mIsWorkProfile = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE)
+ == ProfileSelectFragment.ProfileType.WORK;
+ mUserId = Utils.getCurrentUserId(mUserManager, mIsWorkProfile);
+
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onViewCreated(View v, Bundle savedInstanceState) {
+ super.onViewCreated(v, savedInstanceState);
+ initializeCacheProvider();
+ maybeSetLoading(isQuotaSupported());
+
+ EntityHeaderController.newInstance(getActivity(), this /*fragment*/,
+ null /* header view */)
+ .setRecyclerView(getListView(), getSettingsLifecycle());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mSelectedStorageEntry != null) {
+ refreshUi(mSelectedStorageEntry);
+ }
+ }
+
+ private void onReceivedSizes() {
+ boolean stopLoading = false;
+ if (mStorageInfo != null) {
+ final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
+ mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
+ mPreferenceController.setUsedSize(privateUsedBytes);
+ mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
+ for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
+ final AbstractPreferenceController controller = mSecondaryUsers.get(i);
+ if (controller instanceof SecondaryUserController) {
+ SecondaryUserController userController = (SecondaryUserController) controller;
+ userController.setTotalSize(mStorageInfo.totalBytes);
+ }
+ }
+ stopLoading = true;
+ }
+
+ if (mAppsResult != null) {
+ mPreferenceController.onLoadFinished(mAppsResult, mUserId);
+ updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
+ stopLoading = true;
+ }
+
+ // setLoading always causes a flicker, so let's avoid doing it.
+ if (stopLoading) {
+ if (getView().findViewById(R.id.loading_container).getVisibility() == View.VISIBLE) {
+ setLoading(false, true);
+ }
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_STORAGE_CATEGORY;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.storage_category_fragment;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ final StorageManager sm = context.getSystemService(StorageManager.class);
+ mPreferenceController = new StorageItemPreferenceController(context, this,
+ null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
+ controllers.add(mPreferenceController);
+
+ mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context,
+ mUserManager, mIsWorkProfile /* isWorkProfileOnly */);
+ controllers.addAll(mSecondaryUsers);
+
+ return controllers;
+ }
+
+ /**
+ * Updates the secondary user controller sizes.
+ */
+ private void updateSecondaryUserControllers(List<AbstractPreferenceController> controllers,
+ SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
+ for (int i = 0, size = controllers.size(); i < size; i++) {
+ final AbstractPreferenceController controller = controllers.get(i);
+ if (controller instanceof StorageAsyncLoader.ResultHandler) {
+ StorageAsyncLoader.ResultHandler userController =
+ (StorageAsyncLoader.ResultHandler) controller;
+ userController.handleResult(stats);
+ }
+ }
+ }
+
+ @Override
+ public Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> onCreateLoader(int id,
+ Bundle args) {
+ final Context context = getContext();
+ return new StorageAsyncLoader(context, mUserManager,
+ mSelectedStorageEntry.getFsUuid(),
+ new StorageStatsSource(context),
+ context.getPackageManager());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader,
+ SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
+ mAppsResult = data;
+ maybeCacheFreshValues();
+ onReceivedSizes();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader) {
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mFreeUpSpacePreference) {
+ final Context context = getContext();
+ final MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ metricsFeatureProvider.logClickedPreference(preference, getMetricsCategory());
+ metricsFeatureProvider.action(context, SettingsEnums.STORAGE_FREE_UP_SPACE_NOW);
+ final Intent intent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+ context.startActivityAsUser(intent, new UserHandle(mUserId));
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ public void setCachedStorageValuesHelper(CachedStorageValuesHelper helper) {
+ mCachedStorageValuesHelper = helper;
+ }
+
+ @VisibleForTesting
+ public PrivateStorageInfo getPrivateStorageInfo() {
+ return mStorageInfo;
+ }
+
+ @VisibleForTesting
+ public void setPrivateStorageInfo(PrivateStorageInfo info) {
+ mStorageInfo = info;
+ }
+
+ @VisibleForTesting
+ public SparseArray<StorageAsyncLoader.AppsStorageResult> getAppsStorageResult() {
+ return mAppsResult;
+ }
+
+ @VisibleForTesting
+ public void setAppsStorageResult(SparseArray<StorageAsyncLoader.AppsStorageResult> info) {
+ mAppsResult = info;
+ }
+
+ @VisibleForTesting
+ void initializeCachedValues() {
+ final PrivateStorageInfo info = mCachedStorageValuesHelper.getCachedPrivateStorageInfo();
+ final SparseArray<StorageAsyncLoader.AppsStorageResult> loaderResult =
+ mCachedStorageValuesHelper.getCachedAppsStorageResult();
+ if (info == null || loaderResult == null) {
+ return;
+ }
+
+ mStorageInfo = info;
+ mAppsResult = loaderResult;
+ }
+
+ /**
+ * Activate loading UI and animation if it's necessary.
+ */
+ @VisibleForTesting
+ public void maybeSetLoading(boolean isQuotaSupported) {
+ // If we have fast stats, we load until both have loaded.
+ // If we have slow stats, we load when we get the total volume sizes.
+ if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null))
+ || (!isQuotaSupported && mStorageInfo == null)) {
+ setLoading(true /* loading */, false /* animate */);
+ }
+ }
+
+ private void initializeCacheProvider() {
+ mCachedStorageValuesHelper = new CachedStorageValuesHelper(getContext(), mUserId);
+ initializeCachedValues();
+ onReceivedSizes();
+ }
+
+ private void maybeCacheFreshValues() {
+ if (mStorageInfo != null && mAppsResult != null) {
+ mCachedStorageValuesHelper.cacheResult(mStorageInfo, mAppsResult.get(mUserId));
+ }
+ }
+
+ private boolean isQuotaSupported() {
+ return mSelectedStorageEntry.isMounted()
+ && getActivity().getSystemService(StorageStatsManager.class)
+ .isQuotaSupported(mSelectedStorageEntry.getFsUuid());
+ }
+
+ /**
+ * IconLoaderCallbacks exists because StorageCategoryFragment already implements
+ * LoaderCallbacks for a different type.
+ */
+ public final class IconLoaderCallbacks
+ implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> {
+ @Override
+ public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) {
+ return new UserIconLoader(
+ getContext(),
+ () -> UserIconLoader.loadUserIconsWithContext(getContext()));
+ }
+
+ @Override
+ public void onLoadFinished(
+ Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
+ mSecondaryUsers
+ .stream()
+ .filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
+ .forEach(
+ controller ->
+ ((UserIconLoader.UserIconHandler) controller)
+ .handleUserIcons(data));
+ }
+
+ @Override
+ public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {
+ }
+ }
+
+ /**
+ * VolumeSizeCallbacks exists because StorageCategoryFragment already implements
+ * LoaderCallbacks for a different type.
+ */
+ public final class VolumeSizeCallbacks
+ implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
+ @Override
+ public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
+ final Context context = getContext();
+ final StorageManagerVolumeProvider smvp =
+ new StorageManagerVolumeProvider(mStorageManager);
+ final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
+ return new VolumeSizesLoader(context, smvp, stats,
+ mSelectedStorageEntry.getVolumeInfo());
+ }
+
+ @Override
+ public void onLoaderReset(Loader<PrivateStorageInfo> loader) {
+ }
+
+ @Override
+ public void onLoadFinished(
+ Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
+ if (privateStorageInfo == null) {
+ getActivity().finish();
+ return;
+ }
+
+ mStorageInfo = privateStorageInfo;
+ maybeCacheFreshValues();
+ onReceivedSizes();
+ }
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 6a3bb51..cc7eff6 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -43,7 +43,6 @@
import com.android.settings.R;
import com.android.settings.Utils;
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;
@@ -69,8 +68,20 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
+/**
+ * Storage Settings main UI is composed by 3 fragments:
+ *
+ * StorageDashboardFragment only shows when there is only personal profile for current user.
+ *
+ * ProfileSelectStorageFragment (controls preferences above profile tab) and
+ * StorageCategoryFragment (controls preferences below profile tab) only show when current
+ * user has installed work profile.
+ *
+ * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same
+ * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to
+ * change Storage Settings.
+ */
@SearchIndexable
public class StorageDashboardFragment extends DashboardFragment
implements
@@ -104,7 +115,7 @@
private final StorageEventListener mStorageEventListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo volumeInfo, int oldState, int newState) {
- if (!isInteresting(volumeInfo)) {
+ if (!StorageUtils.isStorageSettingsInterestedVolume(volumeInfo)) {
return;
}
@@ -146,7 +157,7 @@
@Override
public void onVolumeRecordChanged(VolumeRecord volumeRecord) {
- if (isVolumeRecordMissed(volumeRecord)) {
+ if (StorageUtils.isVolumeRecordMissed(mStorageManager, 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.
@@ -159,7 +170,7 @@
// Find mapped VolumeInfo and replace with existing one for something changed.
// (e.g., Renamed.)
final VolumeInfo mappedVolumeInfo =
- mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid());
+ mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid());
if (mappedVolumeInfo == null) {
return;
}
@@ -190,7 +201,7 @@
@Override
public void onDiskScanned(DiskInfo disk, int volumeCount) {
- if (!isDiskUnsupported(disk)) {
+ if (!StorageUtils.isDiskUnsupported(disk)) {
return;
}
final StorageEntry storageEntry = new StorageEntry(disk);
@@ -213,33 +224,6 @@
}
};
- private static boolean isInteresting(VolumeInfo volumeInfo) {
- switch (volumeInfo.getType()) {
- case VolumeInfo.TYPE_PRIVATE:
- case VolumeInfo.TYPE_PUBLIC:
- case VolumeInfo.TYPE_STUB:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * 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;
- }
-
private void refreshUi() {
mStorageSelectionController.setStorageEntries(mStorageEntries);
mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);
@@ -297,9 +281,8 @@
// These member variables are initialized befoer super.onAttach for
// createPreferenceControllers to work correctly.
mUserManager = context.getSystemService(UserManager.class);
- mIsWorkProfile = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE)
- == ProfileSelectFragment.ProfileType.WORK;
- mUserId = Utils.getCurrentUserId(mUserManager, mIsWorkProfile);
+ mIsWorkProfile = false;
+ mUserId = UserHandle.myUserId();
super.onAttach(context);
use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
@@ -334,8 +317,7 @@
initializeCacheProvider();
maybeSetLoading(isQuotaSupported());
- final Activity activity = getActivity();
- EntityHeaderController.newInstance(activity, this /*fragment*/,
+ EntityHeaderController.newInstance(getActivity(), this /*fragment*/,
null /* header view */)
.setRecyclerView(getListView(), getSettingsLifecycle());
}
@@ -345,18 +327,7 @@
super.onResume();
mStorageEntries.clear();
- mStorageEntries.addAll(mStorageManager.getVolumes().stream()
- .filter(volumeInfo -> isInteresting(volumeInfo))
- .map(volumeInfo -> new StorageEntry(getContext(), volumeInfo))
- .collect(Collectors.toList()));
- mStorageEntries.addAll(mStorageManager.getDisks().stream()
- .filter(disk -> isDiskUnsupported(disk))
- .map(disk -> new StorageEntry(disk))
- .collect(Collectors.toList()));
- mStorageEntries.addAll(mStorageManager.getVolumeRecords().stream()
- .filter(volumeRecord -> isVolumeRecordMissed(volumeRecord))
- .map(volumeRecord -> new StorageEntry(volumeRecord))
- .collect(Collectors.toList()));
+ mStorageEntries.addAll(StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
refreshUi();
mStorageManager.registerListener(mStorageEventListener);
}
@@ -381,19 +352,18 @@
private void onReceivedSizes() {
boolean stopLoading = false;
if (mStorageInfo != null) {
- long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
+ final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
mPreferenceController.setUsedSize(privateUsedBytes);
mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
- AbstractPreferenceController controller = mSecondaryUsers.get(i);
+ final AbstractPreferenceController controller = mSecondaryUsers.get(i);
if (controller instanceof SecondaryUserController) {
SecondaryUserController userController = (SecondaryUserController) controller;
userController.setTotalSize(mStorageInfo.totalBytes);
}
}
stopLoading = true;
-
}
if (mAppsResult != null) {
@@ -428,7 +398,7 @@
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
- StorageManager sm = context.getSystemService(StorageManager.class);
+ final StorageManager sm = context.getSystemService(StorageManager.class);
mPreferenceController = new StorageItemPreferenceController(context, this,
null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
controllers.add(mPreferenceController);
@@ -440,18 +410,13 @@
return controllers;
}
- @VisibleForTesting
- protected void setVolume(VolumeInfo info) {
- mSelectedStorageEntry = new StorageEntry(getContext(), info);
- }
-
/**
* Updates the secondary user controller sizes.
*/
private void updateSecondaryUserControllers(List<AbstractPreferenceController> controllers,
SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
for (int i = 0, size = controllers.size(); i < size; i++) {
- AbstractPreferenceController controller = controllers.get(i);
+ final AbstractPreferenceController controller = controllers.get(i);
if (controller instanceof StorageAsyncLoader.ResultHandler) {
StorageAsyncLoader.ResultHandler userController =
(StorageAsyncLoader.ResultHandler) controller;
@@ -552,9 +517,9 @@
}
@VisibleForTesting
- public void initializeCachedValues() {
- PrivateStorageInfo info = mCachedStorageValuesHelper.getCachedPrivateStorageInfo();
- SparseArray<StorageAsyncLoader.AppsStorageResult> loaderResult =
+ void initializeCachedValues() {
+ final PrivateStorageInfo info = mCachedStorageValuesHelper.getCachedPrivateStorageInfo();
+ final SparseArray<StorageAsyncLoader.AppsStorageResult> loaderResult =
mCachedStorageValuesHelper.getCachedAppsStorageResult();
if (info == null || loaderResult == null) {
return;
@@ -564,12 +529,15 @@
mAppsResult = loaderResult;
}
+ /**
+ * Activate loading UI and animation if it's necessary.
+ */
@VisibleForTesting
public void maybeSetLoading(boolean isQuotaSupported) {
// If we have fast stats, we load until both have loaded.
// If we have slow stats, we load when we get the total volume sizes.
- if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null)) ||
- (!isQuotaSupported && mStorageInfo == null)) {
+ if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null))
+ || (!isQuotaSupported && mStorageInfo == null)) {
setLoading(true /* loading */, false /* animate */);
}
}
@@ -622,6 +590,10 @@
}
}
+ /**
+ * VolumeSizeCallbacks exists because StorageCategoryFragment already implements
+ * LoaderCallbacks for a different type.
+ */
public final class VolumeSizeCallbacks
implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
@Override
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 656f337..241c852 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -76,7 +76,7 @@
@VisibleForTesting
static final String VIDEOS_KEY = "pref_videos";
@VisibleForTesting
- static final String AUDIOS_KEY = "pref_audios";
+ static final String AUDIO_KEY = "pref_audio";
@VisibleForTesting
static final String APPS_KEY = "pref_apps";
@VisibleForTesting
@@ -93,7 +93,7 @@
@VisibleForTesting
final Uri mVideosUri;
@VisibleForTesting
- final Uri mAudiosUri;
+ final Uri mAudioUri;
@VisibleForTesting
final Uri mDocumentsAndOtherUri;
@@ -119,7 +119,7 @@
@VisibleForTesting
StorageItemPreference mVideosPreference;
@VisibleForTesting
- StorageItemPreference mAudiosPreference;
+ StorageItemPreference mAudioPreference;
@VisibleForTesting
StorageItemPreference mAppsPreference;
@VisibleForTesting
@@ -151,8 +151,8 @@
.getString(R.string.config_images_storage_category_uri));
mVideosUri = Uri.parse(context.getResources()
.getString(R.string.config_videos_storage_category_uri));
- mAudiosUri = Uri.parse(context.getResources()
- .getString(R.string.config_audios_storage_category_uri));
+ mAudioUri = Uri.parse(context.getResources()
+ .getString(R.string.config_audio_storage_category_uri));
mDocumentsAndOtherUri = Uri.parse(context.getResources()
.getString(R.string.config_documents_and_other_storage_category_uri));
}
@@ -182,8 +182,8 @@
case VIDEOS_KEY:
launchActivityWithUri(mVideosUri);
return true;
- case AUDIOS_KEY:
- launchActivityWithUri(mAudiosUri);
+ case AUDIO_KEY:
+ launchActivityWithUri(mAudioUri);
return true;
case APPS_KEY:
launchAppsIntent();
@@ -251,7 +251,7 @@
final boolean privateStoragePreferencesVisible = isValidPrivateVolume();
mImagesPreference.setVisible(privateStoragePreferencesVisible);
mVideosPreference.setVisible(privateStoragePreferencesVisible);
- mAudiosPreference.setVisible(privateStoragePreferencesVisible);
+ mAudioPreference.setVisible(privateStoragePreferencesVisible);
mAppsPreference.setVisible(privateStoragePreferencesVisible);
mGamesPreference.setVisible(privateStoragePreferencesVisible);
mDocumentsAndOtherPreference.setVisible(privateStoragePreferencesVisible);
@@ -279,7 +279,7 @@
mPrivateStorageItemPreferences.add(mImagesPreference);
mPrivateStorageItemPreferences.add(mVideosPreference);
- mPrivateStorageItemPreferences.add(mAudiosPreference);
+ mPrivateStorageItemPreferences.add(mAudioPreference);
mPrivateStorageItemPreferences.add(mAppsPreference);
mPrivateStorageItemPreferences.add(mGamesPreference);
mPrivateStorageItemPreferences.add(mDocumentsAndOtherPreference);
@@ -288,7 +288,7 @@
}
mScreen.removePreference(mImagesPreference);
mScreen.removePreference(mVideosPreference);
- mScreen.removePreference(mAudiosPreference);
+ mScreen.removePreference(mAudioPreference);
mScreen.removePreference(mAppsPreference);
mScreen.removePreference(mGamesPreference);
mScreen.removePreference(mDocumentsAndOtherPreference);
@@ -317,7 +317,7 @@
tintPreference(mPublicStoragePreference);
tintPreference(mImagesPreference);
tintPreference(mVideosPreference);
- tintPreference(mAudiosPreference);
+ tintPreference(mAudioPreference);
tintPreference(mAppsPreference);
tintPreference(mGamesPreference);
tintPreference(mDocumentsAndOtherPreference);
@@ -346,7 +346,7 @@
mPublicStoragePreference = screen.findPreference(PUBLIC_STORAGE_KEY);
mImagesPreference = screen.findPreference(IMAGES_KEY);
mVideosPreference = screen.findPreference(VIDEOS_KEY);
- mAudiosPreference = screen.findPreference(AUDIOS_KEY);
+ mAudioPreference = screen.findPreference(AUDIO_KEY);
mAppsPreference = screen.findPreference(APPS_KEY);
mGamesPreference = screen.findPreference(GAMES_KEY);
mDocumentsAndOtherPreference = screen.findPreference(DOCUMENTS_AND_OTHER_KEY);
@@ -363,7 +363,7 @@
mImagesPreference.setStorageSize(getImagesSize(data), mTotalSize);
mVideosPreference.setStorageSize(getVideosSize(data), mTotalSize);
- mAudiosPreference.setStorageSize(getAudiosSize(data), mTotalSize);
+ mAudioPreference.setStorageSize(getAudioSize(data), mTotalSize);
mAppsPreference.setStorageSize(getAppsSize(data), mTotalSize);
mGamesPreference.setStorageSize(getGamesSize(data), mTotalSize);
mDocumentsAndOtherPreference.setStorageSize(getDocumentsAndOtherSize(data),
@@ -426,7 +426,7 @@
return data.videoAppsSize;
}
- private long getAudiosSize(StorageAsyncLoader.AppsStorageResult data) {
+ private long getAudioSize(StorageAsyncLoader.AppsStorageResult data) {
return data.musicAppsSize + data.externalStats.audioBytes;
}
diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java
index 919908d..549eef6 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageUtils.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java
@@ -22,6 +22,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.storage.DiskInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
@@ -36,11 +37,67 @@
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.deviceinfo.PrivateVolumeForget;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
/** Storage utilities */
public class StorageUtils {
private static final String TAG = "StorageUtils";
+ /**
+ * Collects and returns all kinds of StorageEntry which will show in Storage Settings.
+ */
+ public static List<StorageEntry> getAllStorageEntries(Context context,
+ StorageManager storageManager) {
+ final List<StorageEntry> storageEntries = new ArrayList<>();
+ storageEntries.addAll(storageManager.getVolumes().stream()
+ .filter(volumeInfo -> isStorageSettingsInterestedVolume(volumeInfo))
+ .map(volumeInfo -> new StorageEntry(context, volumeInfo))
+ .collect(Collectors.toList()));
+ storageEntries.addAll(storageManager.getDisks().stream()
+ .filter(disk -> isDiskUnsupported(disk))
+ .map(disk -> new StorageEntry(disk))
+ .collect(Collectors.toList()));
+ storageEntries.addAll(storageManager.getVolumeRecords().stream()
+ .filter(volumeRecord -> isVolumeRecordMissed(storageManager, volumeRecord))
+ .map(volumeRecord -> new StorageEntry(volumeRecord))
+ .collect(Collectors.toList()));
+ return storageEntries;
+ }
+
+ /**
+ * Returns true if the volumeInfo may be displayed in Storage Settings.
+ */
+ public static boolean isStorageSettingsInterestedVolume(VolumeInfo volumeInfo) {
+ switch (volumeInfo.getType()) {
+ case VolumeInfo.TYPE_PRIVATE:
+ case VolumeInfo.TYPE_PUBLIC:
+ case VolumeInfo.TYPE_STUB:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * VolumeRecord is a metadata of VolumeInfo, this is the case where a VolumeInfo is missing.
+ * (e.g., internal SD card is removed.)
+ */
+ public static boolean isVolumeRecordMissed(StorageManager storageManager,
+ VolumeRecord volumeRecord) {
+ return volumeRecord.getType() == VolumeInfo.TYPE_PRIVATE
+ && storageManager.findVolumeByUuid(volumeRecord.getFsUuid()) == null;
+ }
+
+ /**
+ * A unsupported disk is the disk of problem format, android is not able to mount automatically.
+ */
+ public static boolean isDiskUnsupported(DiskInfo disk) {
+ return disk.volumeCount == 0 && disk.size > 0;
+ }
+
/** Launches the fragment to forget a specified missing volume record. */
public static void launchForgetMissingVolumeRecordFragment(Context context,
StorageEntry storageEntry) {
diff --git a/src/com/android/settings/fuelgauge/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/BatteryDiffEntry.java
index 7b73638..81ad5be 100644
--- a/src/com/android/settings/fuelgauge/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/BatteryDiffEntry.java
@@ -73,6 +73,11 @@
mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
mBatteryHistEntry = batteryHistEntry;
mUserManager = context.getSystemService(UserManager.class);
+ if (foregroundUsageTimeInMs == 0
+ && backgroundUsageTimeInMs == 0
+ && consumePower != 0) {
+ Log.w(TAG, "abnornal BatteryDiffEntry:\n" + this);
+ }
}
/** Sets the total consumed power in a specific time slot. */
diff --git a/src/com/android/settings/fuelgauge/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java
index 37f9e12..e05247c 100644
--- a/src/com/android/settings/fuelgauge/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/ConvertUtils.java
@@ -230,7 +230,7 @@
if (selectedBatteryEntry == null) {
continue;
}
- // Force refine the cumulative value since it may introduce deviation
+ // Forces refine the cumulative value since it may introduce deviation
// error since we will apply the interpolation arithmetic.
final float totalUsageTimeInMs =
foregroundUsageTimeInMs + backgroundUsageTimeInMs;
diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
index f627e31..04317a8 100644
--- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java
+++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
@@ -74,7 +74,7 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- holder.setDividerAllowedAbove(true);
+ holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
mMainSwitchBar = (SettingsMainSwitchBar) holder.findViewById(R.id.main_switch_bar);
diff --git a/tests/robotests/assets/exempt_not_implementing_index_provider b/tests/robotests/assets/exempt_not_implementing_index_provider
index 7815a48..d4a1c2e 100644
--- a/tests/robotests/assets/exempt_not_implementing_index_provider
+++ b/tests/robotests/assets/exempt_not_implementing_index_provider
@@ -37,6 +37,7 @@
com.android.settings.datetime.timezone.TimeZoneSettings
com.android.settings.development.compat.PlatformCompatDashboard
com.android.settings.deviceinfo.PublicVolumeSettings
+com.android.settings.deviceinfo.StorageDashboardNoHeaderFragment
com.android.settings.deviceinfo.legal.ModuleLicensesDashboard
com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionCamera
com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionLocation
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 3fd1218..0a87d08 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -112,8 +112,8 @@
images.setIcon(R.drawable.ic_photo_library);
final StorageItemPreference videos = spy(new StorageItemPreference(mContext));
videos.setIcon(R.drawable.ic_local_movies);
- final StorageItemPreference audios = spy(new StorageItemPreference(mContext));
- audios.setIcon(R.drawable.ic_media_stream);
+ final StorageItemPreference audio = spy(new StorageItemPreference(mContext));
+ audio.setIcon(R.drawable.ic_media_stream);
final StorageItemPreference apps = spy(new StorageItemPreference(mContext));
apps.setIcon(R.drawable.ic_storage_apps);
final StorageItemPreference games = spy(new StorageItemPreference(mContext));
@@ -132,8 +132,8 @@
.thenReturn(images);
when(screen.findPreference(eq(StorageItemPreferenceController.VIDEOS_KEY)))
.thenReturn(videos);
- when(screen.findPreference(eq(StorageItemPreferenceController.AUDIOS_KEY)))
- .thenReturn(audios);
+ when(screen.findPreference(eq(StorageItemPreferenceController.AUDIO_KEY)))
+ .thenReturn(audio);
when(screen.findPreference(eq(StorageItemPreferenceController.APPS_KEY)))
.thenReturn(apps);
when(screen.findPreference(eq(StorageItemPreferenceController.GAMES_KEY)))
@@ -190,8 +190,8 @@
}
@Test
- public void launchAudiosIntent_resolveActionViewNull_settingsIntent() {
- mPreference.setKey(StorageItemPreferenceController.AUDIOS_KEY);
+ public void launchAudioIntent_resolveActionViewNull_settingsIntent() {
+ mPreference.setKey(StorageItemPreferenceController.AUDIO_KEY);
final Context mockContext = getMockContext();
mController = new StorageItemPreferenceController(mockContext, mFragment, mVolume,
mSvp, false /* isWorkProfile */);
@@ -203,7 +203,7 @@
final Intent intent = argumentCaptor.getValue();
assertThat(intent.getAction()).isEqualTo(Intent.ACTION_VIEW);
- assertThat(intent.getData()).isEqualTo(mController.mAudiosUri);
+ assertThat(intent.getData()).isEqualTo(mController.mAudioUri);
}
@Test
@@ -215,7 +215,7 @@
assertThat(mController.mPublicStoragePreference.isVisible()).isFalse();
assertThat(mController.mImagesPreference.isVisible()).isFalse();
assertThat(mController.mVideosPreference.isVisible()).isFalse();
- assertThat(mController.mAudiosPreference.isVisible()).isFalse();
+ assertThat(mController.mAudioPreference.isVisible()).isFalse();
assertThat(mController.mAppsPreference.isVisible()).isFalse();
assertThat(mController.mGamesPreference.isVisible()).isFalse();
assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isFalse();
@@ -355,7 +355,7 @@
assertThat(mController.mImagesPreference.getSummary().toString()).isEqualTo("0.35 GB");
assertThat(mController.mVideosPreference.getSummary().toString()).isEqualTo("0.16 GB");
- assertThat(mController.mAudiosPreference.getSummary().toString()).isEqualTo("0.14 GB");
+ assertThat(mController.mAudioPreference.getSummary().toString()).isEqualTo("0.14 GB");
assertThat(mController.mAppsPreference.getSummary().toString()).isEqualTo("0.09 GB");
assertThat(mController.mGamesPreference.getSummary().toString()).isEqualTo("0.08 GB");
assertThat(mController.mDocumentsAndOtherPreference.getSummary().toString())
@@ -371,7 +371,7 @@
verify(mController.mPublicStoragePreference, times(2)).setIcon(nullable(Drawable.class));
verify(mController.mImagesPreference, times(2)).setIcon(nullable(Drawable.class));
verify(mController.mVideosPreference, times(2)).setIcon(nullable(Drawable.class));
- verify(mController.mAudiosPreference, times(2)).setIcon(nullable(Drawable.class));
+ verify(mController.mAudioPreference, times(2)).setIcon(nullable(Drawable.class));
verify(mController.mAppsPreference, times(2)).setIcon(nullable(Drawable.class));
verify(mController.mGamesPreference, times(2)).setIcon(nullable(Drawable.class));
verify(mController.mDocumentsAndOtherPreference, times(2))
@@ -455,7 +455,7 @@
assertThat(mController.mPublicStoragePreference.isVisible()).isTrue();
assertThat(mController.mImagesPreference.isVisible()).isFalse();
assertThat(mController.mVideosPreference.isVisible()).isFalse();
- assertThat(mController.mAudiosPreference.isVisible()).isFalse();
+ assertThat(mController.mAudioPreference.isVisible()).isFalse();
assertThat(mController.mAppsPreference.isVisible()).isFalse();
assertThat(mController.mGamesPreference.isVisible()).isFalse();
assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isFalse();
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoLoaderTest.java
index 5bcaf0a..0dfabb9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoLoaderTest.java
@@ -78,6 +78,6 @@
BatteryInfo info = loader.loadInBackground();
assertThat(info.remainingLabel).isNotNull();
- assertThat(info.remainingTimeUs).isEqualTo(TEST_TIME_REMAINING);
+ assertThat(info.remainingTimeUs).isEqualTo(TEST_TIME_REMAINING * 1000);
}
}
diff --git a/tests/unit/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragmentTest.java b/tests/unit/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragmentTest.java
new file mode 100644
index 0000000..6eb9aa5
--- /dev/null
+++ b/tests/unit/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragmentTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.dashboard.profileselector;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.os.Looper;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class ProfileSelectStorageFragmentTest {
+
+ private ProfileSelectStorageFragment mFragment;
+
+ @Before
+ @UiThreadTest
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFragment = new ProfileSelectStorageFragment();
+ }
+
+ @Test
+ @UiThreadTest
+ public void test_initializeOptionsMenuInvalidatesExistingMenu() {
+ final Activity activity = mock(Activity.class);
+
+ mFragment.initializeOptionsMenu(activity);
+
+ verify(activity).invalidateOptionsMenu();
+ }
+}