blob: 5babf30c185239a7327ef56789c06b61d7673d9e [file] [log] [blame]
/*
* 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 android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
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.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.StorageCacheHelper;
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 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 boolean mIsLoadedFromCache;
private StorageCacheHelper mStorageCacheHelper;
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);
final int volumeState = volumeInfo.getState();
switch (volumeState) {
case VolumeInfo.STATE_REMOVED:
case VolumeInfo.STATE_BAD_REMOVAL:
// Remove removed storage from list and don't show it on spinner.
if (!mStorageEntries.remove(changedStorageEntry)) {
break;
}
case VolumeInfo.STATE_MOUNTED:
case VolumeInfo.STATE_MOUNTED_READ_ONLY:
case VolumeInfo.STATE_UNMOUNTABLE:
case VolumeInfo.STATE_UNMOUNTED:
case VolumeInfo.STATE_EJECTING:
// 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);
});
if (volumeState != VolumeInfo.STATE_REMOVED
&& volumeState != VolumeInfo.STATE_BAD_REMOVAL) {
mStorageEntries.add(changedStorageEntry);
}
if (changedStorageEntry.equals(mSelectedStorageEntry)) {
mSelectedStorageEntry = changedStorageEntry;
}
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 StorageCategoryFragment();
workFragment.setArguments(workBundle);
final Bundle personalBundle = new Bundle();
personalBundle.putInt(EXTRA_PROFILE, ProfileType.PERSONAL);
final Fragment personalFragment = new StorageCategoryFragment();
personalFragment.setArguments(personalBundle);
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);
if (mStorageCacheHelper.hasCachedSizeInfo()) {
mIsLoadedFromCache = true;
mStorageEntries.clear();
mStorageEntries.addAll(
StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
refreshUi();
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mStorageCacheHelper = new StorageCacheHelper(getContext(), UserHandle.myUserId());
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();
if (mIsLoadedFromCache) {
mIsLoadedFromCache = false;
} else {
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_PROFILE_SELECTOR;
}
@Override
protected String getLogTag() {
return TAG;
}
}