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