| /* |
| * Copyright (C) 2018 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.tv.settings.device; |
| |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.storage.DiskInfo; |
| import android.os.storage.StorageManager; |
| import android.os.storage.VolumeInfo; |
| import android.os.storage.VolumeRecord; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import androidx.annotation.Keep; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceCategory; |
| |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.tv.settings.R; |
| import com.android.tv.settings.SettingsPreferenceFragment; |
| import com.android.tv.settings.device.storage.MissingStorageFragment; |
| import com.android.tv.settings.device.storage.NewStorageActivity; |
| import com.android.tv.settings.device.storage.StorageFragment; |
| import com.android.tv.settings.device.storage.StoragePreference; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * The "Storage" screen in TV settings. |
| */ |
| @Keep |
| public class StorageSummaryFragment extends SettingsPreferenceFragment { |
| private static final String TAG = "StorageSummaryFragment"; |
| |
| private static final String KEY_DEVICE_CATEGORY = "device_storage"; |
| private static final String KEY_REMOVABLE_CATEGORY = "removable_storage"; |
| |
| private static final int REFRESH_DELAY_MILLIS = 500; |
| |
| private StorageManager mStorageManager; |
| private final StorageSummaryFragment.StorageEventListener |
| mStorageEventListener = new StorageSummaryFragment.StorageEventListener(); |
| |
| private final Handler mHandler = new Handler(); |
| private final Runnable mRefreshRunnable = new Runnable() { |
| @Override |
| public void run() { |
| refresh(); |
| } |
| }; |
| |
| public static StorageSummaryFragment newInstance() { |
| return new StorageSummaryFragment(); |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| mStorageManager = getContext().getSystemService(StorageManager.class); |
| super.onCreate(savedInstanceState); |
| } |
| |
| @Override |
| public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { |
| setPreferencesFromResource(R.xml.storage_summary, null); |
| findPreference(KEY_REMOVABLE_CATEGORY).setVisible(false); |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| mStorageManager.registerListener(mStorageEventListener); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| mHandler.removeCallbacks(mRefreshRunnable); |
| // Delay to allow entrance animations to complete |
| mHandler.postDelayed(mRefreshRunnable, REFRESH_DELAY_MILLIS); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| mHandler.removeCallbacks(mRefreshRunnable); |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| mStorageManager.unregisterListener(mStorageEventListener); |
| } |
| |
| private void refresh() { |
| if (!isResumed()) { |
| return; |
| } |
| final Context themedContext = getPreferenceManager().getContext(); |
| |
| final List<VolumeInfo> volumes = mStorageManager.getVolumes(); |
| volumes.sort(VolumeInfo.getDescriptionComparator()); |
| |
| final List<VolumeInfo> privateVolumes = new ArrayList<>(volumes.size()); |
| final List<VolumeInfo> publicVolumes = new ArrayList<>(volumes.size()); |
| |
| // Find mounted volumes |
| for (final VolumeInfo vol : volumes) { |
| if (vol.getType() == VolumeInfo.TYPE_PRIVATE) { |
| privateVolumes.add(vol); |
| } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) { |
| publicVolumes.add(vol); |
| } else { |
| Log.d(TAG, "Skipping volume " + vol.toString()); |
| } |
| } |
| |
| // Find missing private filesystems |
| final List<VolumeRecord> volumeRecords = mStorageManager.getVolumeRecords(); |
| final List<VolumeRecord> privateMissingVolumes = new ArrayList<>(volumeRecords.size()); |
| |
| for (final VolumeRecord record : volumeRecords) { |
| if (record.getType() == VolumeInfo.TYPE_PRIVATE |
| && mStorageManager.findVolumeByUuid(record.getFsUuid()) == null) { |
| privateMissingVolumes.add(record); |
| } |
| } |
| |
| // Find unreadable disks |
| final List<DiskInfo> disks = mStorageManager.getDisks(); |
| final List<DiskInfo> unsupportedDisks = new ArrayList<>(disks.size()); |
| for (final DiskInfo disk : disks) { |
| if (disk.volumeCount == 0 && disk.size > 0) { |
| unsupportedDisks.add(disk); |
| } |
| } |
| |
| // Add the prefs |
| final PreferenceCategory deviceCategory = |
| (PreferenceCategory) findPreference(KEY_DEVICE_CATEGORY); |
| final Set<String> touchedDeviceKeys = |
| new ArraySet<>(privateVolumes.size() + privateMissingVolumes.size()); |
| |
| for (final VolumeInfo volumeInfo : privateVolumes) { |
| final String key = StorageSummaryFragment.VolPreference.makeKey(volumeInfo); |
| touchedDeviceKeys.add(key); |
| StorageSummaryFragment.VolPreference volPreference = |
| (StorageSummaryFragment.VolPreference) deviceCategory.findPreference(key); |
| if (volPreference == null) { |
| volPreference = new StorageSummaryFragment.VolPreference(themedContext, volumeInfo); |
| } |
| volPreference.refresh(themedContext, mStorageManager, volumeInfo); |
| deviceCategory.addPreference(volPreference); |
| } |
| |
| for (final VolumeRecord volumeRecord : privateMissingVolumes) { |
| final String key = StorageSummaryFragment.MissingPreference.makeKey(volumeRecord); |
| touchedDeviceKeys.add(key); |
| StorageSummaryFragment.MissingPreference missingPreference = |
| (StorageSummaryFragment.MissingPreference) deviceCategory.findPreference(key); |
| if (missingPreference == null) { |
| missingPreference = new StorageSummaryFragment.MissingPreference( |
| themedContext, volumeRecord); |
| } |
| deviceCategory.addPreference(missingPreference); |
| } |
| |
| for (int i = 0; i < deviceCategory.getPreferenceCount();) { |
| final Preference pref = deviceCategory.getPreference(i); |
| if (touchedDeviceKeys.contains(pref.getKey())) { |
| i++; |
| } else { |
| deviceCategory.removePreference(pref); |
| } |
| } |
| |
| final PreferenceCategory removableCategory = |
| (PreferenceCategory) findPreference(KEY_REMOVABLE_CATEGORY); |
| final int publicCount = publicVolumes.size() + unsupportedDisks.size(); |
| final Set<String> touchedRemovableKeys = new ArraySet<>(publicCount); |
| // Only show section if there are public/unknown volumes present |
| removableCategory.setVisible(publicCount > 0); |
| |
| for (final VolumeInfo volumeInfo : publicVolumes) { |
| final String key = StorageSummaryFragment.VolPreference.makeKey(volumeInfo); |
| touchedRemovableKeys.add(key); |
| StorageSummaryFragment.VolPreference volPreference = |
| (StorageSummaryFragment.VolPreference) removableCategory.findPreference(key); |
| if (volPreference == null) { |
| volPreference = new StorageSummaryFragment.VolPreference(themedContext, volumeInfo); |
| } |
| volPreference.refresh(themedContext, mStorageManager, volumeInfo); |
| removableCategory.addPreference(volPreference); |
| } |
| for (final DiskInfo diskInfo : unsupportedDisks) { |
| final String key = StorageSummaryFragment.UnsupportedDiskPreference.makeKey(diskInfo); |
| touchedRemovableKeys.add(key); |
| StorageSummaryFragment.UnsupportedDiskPreference unsupportedDiskPreference = |
| (StorageSummaryFragment.UnsupportedDiskPreference) findPreference(key); |
| if (unsupportedDiskPreference == null) { |
| unsupportedDiskPreference = new StorageSummaryFragment.UnsupportedDiskPreference( |
| themedContext, diskInfo); |
| } |
| removableCategory.addPreference(unsupportedDiskPreference); |
| } |
| |
| for (int i = 0; i < removableCategory.getPreferenceCount();) { |
| final Preference pref = removableCategory.getPreference(i); |
| if (touchedRemovableKeys.contains(pref.getKey())) { |
| i++; |
| } else { |
| removableCategory.removePreference(pref); |
| } |
| } |
| } |
| |
| private static class VolPreference extends Preference { |
| VolPreference(Context context, VolumeInfo volumeInfo) { |
| super(context); |
| setKey(makeKey(volumeInfo)); |
| } |
| |
| private void refresh(Context context, StorageManager storageManager, |
| VolumeInfo volumeInfo) { |
| final String description = storageManager |
| .getBestVolumeDescription(volumeInfo); |
| setTitle(description); |
| if (volumeInfo.isMountedReadable()) { |
| setSummary(getSizeString(volumeInfo)); |
| setFragment(StorageFragment.class.getName()); |
| StorageFragment.prepareArgs(getExtras(), volumeInfo); |
| } else { |
| setSummary(context.getString(R.string.storage_unmount_success, description)); |
| } |
| } |
| |
| private String getSizeString(VolumeInfo vol) { |
| final File path = vol.getPath(); |
| if (vol.isMountedReadable() && path != null) { |
| return String.format(getContext().getString(R.string.storage_size), |
| StoragePreference.formatSize(getContext(), path.getTotalSpace())); |
| } else { |
| return null; |
| } |
| } |
| |
| public static String makeKey(VolumeInfo volumeInfo) { |
| return "VolPref:" + volumeInfo.getId(); |
| } |
| } |
| |
| private static class MissingPreference extends Preference { |
| MissingPreference(Context context, VolumeRecord volumeRecord) { |
| super(context); |
| setKey(makeKey(volumeRecord)); |
| setTitle(volumeRecord.getNickname()); |
| setSummary(R.string.storage_not_connected); |
| setFragment(MissingStorageFragment.class.getName()); |
| MissingStorageFragment.prepareArgs(getExtras(), volumeRecord.getFsUuid()); |
| } |
| |
| public static String makeKey(VolumeRecord volumeRecord) { |
| return "MissingPref:" + volumeRecord.getFsUuid(); |
| } |
| } |
| |
| private static class UnsupportedDiskPreference extends Preference { |
| UnsupportedDiskPreference(Context context, DiskInfo info) { |
| super(context); |
| setKey(makeKey(info)); |
| setTitle(info.getDescription()); |
| setIntent(NewStorageActivity.getNewStorageLaunchIntent(context, null, info.getId())); |
| } |
| |
| public static String makeKey(DiskInfo info) { |
| return "UnsupportedPref:" + info.getId(); |
| } |
| } |
| |
| private class StorageEventListener extends android.os.storage.StorageEventListener { |
| @Override |
| public void onStorageStateChanged(String path, String oldState, String newState) { |
| refresh(); |
| } |
| |
| @Override |
| public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { |
| refresh(); |
| } |
| |
| @Override |
| public void onVolumeRecordChanged(VolumeRecord rec) { |
| refresh(); |
| } |
| |
| @Override |
| public void onVolumeForgotten(String fsUuid) { |
| refresh(); |
| } |
| |
| @Override |
| public void onDiskScanned(DiskInfo disk, int volumeCount) { |
| refresh(); |
| } |
| |
| @Override |
| public void onDiskDestroyed(DiskInfo disk) { |
| refresh(); |
| } |
| |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsProto.MetricsEvent.SETTINGS_STORAGE_CATEGORY; |
| } |
| } |