1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.tv.settings.device.storage;
18 
19 import android.app.ActivityManager;
20 import android.content.pm.PackageManager;
21 import android.os.Bundle;
22 import android.os.Environment;
23 import android.os.storage.StorageManager;
24 import android.os.storage.VolumeInfo;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceScreen;
30 
31 import com.android.internal.logging.nano.MetricsProto;
32 import com.android.settingslib.deviceinfo.StorageMeasurement;
33 import com.android.tv.settings.R;
34 import com.android.tv.settings.SettingsPreferenceFragment;
35 import com.android.tv.settings.device.apps.AppsFragment;
36 
37 import java.util.HashMap;
38 import java.util.List;
39 
40 /**
41  * The screen in TV settings that lets users manage external storage devices and that shows the
42  * usage summary by data type such as apps, music, download and so on.
43  */
44 public class StorageFragment extends SettingsPreferenceFragment {
45     private static final String TAG = "StorageFragment";
46 
47     private static final String KEY_MIGRATE = "migrate";
48     private static final String KEY_EJECT = "eject";
49     private static final String KEY_ERASE = "erase";
50     private static final String KEY_APPS_USAGE = "apps_usage";
51     private static final String KEY_DCIM_USAGE = "dcim_usage";
52     private static final String KEY_MUSIC_USAGE = "music_usage";
53     private static final String KEY_DOWNLOADS_USAGE = "downloads_usage";
54     private static final String KEY_CACHE_USAGE = "cache_usage";
55     private static final String KEY_MISC_USAGE = "misc_usage";
56     private static final String KEY_AVAILABLE = "available";
57 
58     private StorageManager mStorageManager;
59     private PackageManager mPackageManager;
60 
61     private VolumeInfo mVolumeInfo;
62 
63     private StorageMeasurement mMeasure;
64     private final StorageMeasurement.MeasurementReceiver mMeasurementReceiver =
65             new MeasurementReceiver();
66     private final StorageEventListener mStorageEventListener = new StorageEventListener();
67 
68     private Preference mMigratePref;
69     private Preference mEjectPref;
70     private Preference mErasePref;
71     private StoragePreference mAppsUsagePref;
72     private StoragePreference mDcimUsagePref;
73     private StoragePreference mMusicUsagePref;
74     private StoragePreference mDownloadsUsagePref;
75     private StoragePreference mCacheUsagePref;
76     private StoragePreference mMiscUsagePref;
77     private StoragePreference mAvailablePref;
78 
prepareArgs(Bundle bundle, VolumeInfo volumeInfo)79     public static void prepareArgs(Bundle bundle, VolumeInfo volumeInfo) {
80         bundle.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeInfo.getId());
81     }
82 
83     @Override
onCreate(Bundle savedInstanceState)84     public void onCreate(Bundle savedInstanceState) {
85         mStorageManager = getContext().getSystemService(StorageManager.class);
86         mPackageManager = getContext().getPackageManager();
87 
88         mVolumeInfo = mStorageManager.findVolumeById(
89                 getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID));
90 
91         super.onCreate(savedInstanceState);
92     }
93 
94     @Override
onStart()95     public void onStart() {
96         super.onStart();
97         mStorageManager.registerListener(mStorageEventListener);
98         startMeasurement();
99     }
100 
101     @Override
onResume()102     public void onResume() {
103         super.onResume();
104         mVolumeInfo = mStorageManager.findVolumeById(
105                 getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID));
106         if (mVolumeInfo == null || !mVolumeInfo.isMountedReadable()) {
107             getFragmentManager().popBackStack();
108         } else {
109             refresh();
110         }
111     }
112 
113     @Override
onStop()114     public void onStop() {
115         super.onStop();
116         mStorageManager.unregisterListener(mStorageEventListener);
117         stopMeasurement();
118     }
119 
120     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)121     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
122         setPreferencesFromResource(R.xml.storage, null);
123 
124         final PreferenceScreen screen = getPreferenceScreen();
125         screen.setTitle(mStorageManager.getBestVolumeDescription(mVolumeInfo));
126 
127         mMigratePref = findPreference(KEY_MIGRATE);
128         mEjectPref = findPreference(KEY_EJECT);
129         mErasePref = findPreference(KEY_ERASE);
130 
131         mAppsUsagePref = (StoragePreference) findPreference(KEY_APPS_USAGE);
132         mDcimUsagePref = (StoragePreference) findPreference(KEY_DCIM_USAGE);
133         mMusicUsagePref = (StoragePreference) findPreference(KEY_MUSIC_USAGE);
134         mDownloadsUsagePref = (StoragePreference) findPreference(KEY_DOWNLOADS_USAGE);
135         mCacheUsagePref = (StoragePreference) findPreference(KEY_CACHE_USAGE);
136         mMiscUsagePref = (StoragePreference) findPreference(KEY_MISC_USAGE);
137         mAvailablePref = (StoragePreference) findPreference(KEY_AVAILABLE);
138     }
139 
refresh()140     private void refresh() {
141         boolean showMigrate = false;
142         final VolumeInfo currentExternal = mPackageManager.getPrimaryStorageCurrentVolume();
143         // currentExternal will be null if the drive is not mounted. Don't offer the option to
144         // migrate if so.
145         if (currentExternal != null
146                 && !TextUtils.equals(currentExternal.getId(), mVolumeInfo.getId())) {
147             final List<VolumeInfo> candidates =
148                     mPackageManager.getPrimaryStorageCandidateVolumes();
149             for (final VolumeInfo candidate : candidates) {
150                 if (TextUtils.equals(candidate.getId(), mVolumeInfo.getId())) {
151                     showMigrate = true;
152                     break;
153                 }
154             }
155         }
156 
157         mMigratePref.setVisible(showMigrate);
158         mMigratePref.setIntent(
159                 MigrateStorageActivity.getLaunchIntent(getContext(), mVolumeInfo.getId(), true));
160 
161         final String description = mStorageManager.getBestVolumeDescription(mVolumeInfo);
162 
163         final boolean privateInternal = VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolumeInfo.getId());
164         final boolean isPrivate = mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE;
165 
166         mEjectPref.setVisible(!privateInternal);
167         mEjectPref.setIntent(UnmountActivity.getIntent(getContext(), mVolumeInfo.getId(),
168                 description));
169         mErasePref.setVisible(!privateInternal);
170         if (isPrivate) {
171             mErasePref.setIntent(
172                     FormatActivity.getFormatAsPublicIntent(getContext(), mVolumeInfo.getDiskId()));
173             mErasePref.setTitle(R.string.storage_format_as_public);
174         } else {
175             mErasePref.setIntent(
176                     FormatActivity.getFormatAsPrivateIntent(getContext(), mVolumeInfo.getDiskId()));
177             mErasePref.setTitle(R.string.storage_format_as_private);
178         }
179 
180         mAppsUsagePref.setVisible(isPrivate);
181         mAppsUsagePref.setFragment(AppsFragment.class.getName());
182         AppsFragment.prepareArgs(mAppsUsagePref.getExtras(), mVolumeInfo.fsUuid, description);
183         mDcimUsagePref.setVisible(isPrivate);
184         mMusicUsagePref.setVisible(isPrivate);
185         mDownloadsUsagePref.setVisible(isPrivate);
186         mCacheUsagePref.setVisible(isPrivate);
187         mCacheUsagePref.setFragment(ConfirmClearCacheFragment.class.getName());
188     }
189 
startMeasurement()190     private void startMeasurement() {
191         if (mVolumeInfo != null && mVolumeInfo.isMountedReadable()) {
192             final VolumeInfo sharedVolume = mStorageManager.findEmulatedForPrivate(mVolumeInfo);
193             mMeasure = new StorageMeasurement(getContext(), mVolumeInfo, sharedVolume);
194             mMeasure.setReceiver(mMeasurementReceiver);
195             mMeasure.forceMeasure();
196         }
197     }
198 
stopMeasurement()199     private void stopMeasurement() {
200         if (mMeasure != null) {
201             mMeasure.onDestroy();
202         }
203     }
204 
updateDetails(StorageMeasurement.MeasurementDetails details)205     private void updateDetails(StorageMeasurement.MeasurementDetails details) {
206         final int currentUser = ActivityManager.getCurrentUser();
207         final long dcimSize = totalValues(details.mediaSize.get(currentUser),
208                 Environment.DIRECTORY_DCIM,
209                 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
210 
211         final long musicSize = totalValues(details.mediaSize.get(currentUser),
212                 Environment.DIRECTORY_MUSIC,
213                 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
214                 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
215 
216         final long downloadsSize = totalValues(details.mediaSize.get(currentUser),
217                 Environment.DIRECTORY_DOWNLOADS);
218 
219         mAvailablePref.setSize(details.availSize);
220         mAppsUsagePref.setSize(details.appsSize.get(currentUser));
221         mDcimUsagePref.setSize(dcimSize);
222         mMusicUsagePref.setSize(musicSize);
223         mDownloadsUsagePref.setSize(downloadsSize);
224         mCacheUsagePref.setSize(details.cacheSize);
225         mMiscUsagePref.setSize(details.miscSize.get(currentUser));
226     }
227 
totalValues(HashMap<String, Long> map, String... keys)228     private static long totalValues(HashMap<String, Long> map, String... keys) {
229         long total = 0;
230         if (map != null) {
231             for (String key : keys) {
232                 if (map.containsKey(key)) {
233                     total += map.get(key);
234                 }
235             }
236         } else {
237             Log.w(TAG,
238                     "MeasurementDetails mediaSize array does not have key for current user " +
239                             ActivityManager.getCurrentUser());
240         }
241         return total;
242     }
243 
244     private class MeasurementReceiver implements StorageMeasurement.MeasurementReceiver {
245 
246         @Override
onDetailsChanged(StorageMeasurement.MeasurementDetails details)247         public void onDetailsChanged(StorageMeasurement.MeasurementDetails details) {
248             updateDetails(details);
249         }
250     }
251 
252     private class StorageEventListener extends android.os.storage.StorageEventListener {
253         @Override
onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)254         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
255             mVolumeInfo = vol;
256             if (isResumed()) {
257                 if (mVolumeInfo.isMountedReadable()) {
258                     refresh();
259                 } else {
260                     getFragmentManager().popBackStack();
261                 }
262             }
263         }
264     }
265 
266     @Override
getMetricsCategory()267     public int getMetricsCategory() {
268         return MetricsProto.MetricsEvent.SETTINGS_STORAGE_CATEGORY;
269     }
270 }
271