• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.settings.deviceinfo.storage;
18 
19 import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.PERSONAL_TAB;
20 import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.WORK_TAB;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.res.TypedArray;
26 import android.graphics.drawable.Drawable;
27 import android.net.TrafficStats;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.os.storage.VolumeInfo;
33 import android.util.Log;
34 import android.util.SparseArray;
35 import android.widget.Toast;
36 
37 import androidx.annotation.VisibleForTesting;
38 import androidx.fragment.app.Fragment;
39 import androidx.preference.Preference;
40 import androidx.preference.PreferenceScreen;
41 
42 import com.android.settings.R;
43 import com.android.settings.Settings;
44 import com.android.settings.SettingsActivity;
45 import com.android.settings.Utils;
46 import com.android.settings.applications.manageapplications.ManageApplications;
47 import com.android.settings.core.PreferenceControllerMixin;
48 import com.android.settings.core.SubSettingLauncher;
49 import com.android.settings.deviceinfo.StorageItemPreference;
50 import com.android.settings.deviceinfo.storage.StorageUtils.SystemInfoFragment;
51 import com.android.settings.overlay.FeatureFactory;
52 import com.android.settingslib.core.AbstractPreferenceController;
53 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
54 import com.android.settingslib.deviceinfo.StorageMeasurement;
55 import com.android.settingslib.deviceinfo.StorageVolumeProvider;
56 
57 import java.util.ArrayList;
58 import java.util.Collections;
59 import java.util.Comparator;
60 import java.util.List;
61 import java.util.Map;
62 
63 /**
64  * StorageItemPreferenceController handles the storage line items which summarize the storage
65  * categorization breakdown.
66  */
67 public class StorageItemPreferenceController extends AbstractPreferenceController implements
68         PreferenceControllerMixin,
69         EmptyTrashFragment.OnEmptyTrashCompleteListener {
70     private static final String TAG = "StorageItemPreference";
71 
72     private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo";
73 
74     @VisibleForTesting
75     static final String PUBLIC_STORAGE_KEY = "pref_public_storage";
76     @VisibleForTesting
77     static final String IMAGES_KEY = "pref_images";
78     @VisibleForTesting
79     static final String VIDEOS_KEY = "pref_videos";
80     @VisibleForTesting
81     static final String AUDIO_KEY = "pref_audio";
82     @VisibleForTesting
83     static final String APPS_KEY = "pref_apps";
84     @VisibleForTesting
85     static final String GAMES_KEY = "pref_games";
86     @VisibleForTesting
87     static final String DOCUMENTS_AND_OTHER_KEY = "pref_documents_and_other";
88     @VisibleForTesting
89     static final String SYSTEM_KEY = "pref_system";
90     @VisibleForTesting
91     static final String TRASH_KEY = "pref_trash";
92 
93     @VisibleForTesting
94     final Uri mImagesUri;
95     @VisibleForTesting
96     final Uri mVideosUri;
97     @VisibleForTesting
98     final Uri mAudioUri;
99     @VisibleForTesting
100     final Uri mDocumentsAndOtherUri;
101 
102     // This value should align with the design of storage_dashboard_fragment.xml
103     private static final int LAST_STORAGE_CATEGORY_PREFERENCE_ORDER = 200;
104 
105     private PackageManager mPackageManager;
106     private UserManager mUserManager;
107     private final Fragment mFragment;
108     private final MetricsFeatureProvider mMetricsFeatureProvider;
109     private final StorageVolumeProvider mSvp;
110     private VolumeInfo mVolume;
111     private int mUserId;
112     private long mUsedBytes;
113     private long mTotalSize;
114 
115     private List<StorageItemPreference> mPrivateStorageItemPreferences;
116     private PreferenceScreen mScreen;
117     @VisibleForTesting
118     Preference mPublicStoragePreference;
119     @VisibleForTesting
120     StorageItemPreference mImagesPreference;
121     @VisibleForTesting
122     StorageItemPreference mVideosPreference;
123     @VisibleForTesting
124     StorageItemPreference mAudioPreference;
125     @VisibleForTesting
126     StorageItemPreference mAppsPreference;
127     @VisibleForTesting
128     StorageItemPreference mGamesPreference;
129     @VisibleForTesting
130     StorageItemPreference mDocumentsAndOtherPreference;
131     @VisibleForTesting
132     StorageItemPreference mSystemPreference;
133     @VisibleForTesting
134     StorageItemPreference mTrashPreference;
135 
136     private boolean mIsWorkProfile;
137 
138     private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
139 
StorageItemPreferenceController(Context context, Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile)140     public StorageItemPreferenceController(Context context, Fragment hostFragment,
141             VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile) {
142         super(context);
143         mPackageManager = context.getPackageManager();
144         mUserManager = context.getSystemService(UserManager.class);
145         mFragment = hostFragment;
146         mVolume = volume;
147         mSvp = svp;
148         mIsWorkProfile = isWorkProfile;
149         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
150         mUserId = getCurrentUserId();
151 
152         mImagesUri = Uri.parse(context.getResources()
153                 .getString(R.string.config_images_storage_category_uri));
154         mVideosUri = Uri.parse(context.getResources()
155                 .getString(R.string.config_videos_storage_category_uri));
156         mAudioUri = Uri.parse(context.getResources()
157                 .getString(R.string.config_audio_storage_category_uri));
158         mDocumentsAndOtherUri = Uri.parse(context.getResources()
159                 .getString(R.string.config_documents_and_other_storage_category_uri));
160     }
161 
162     @VisibleForTesting
getCurrentUserId()163     int getCurrentUserId() {
164         return Utils.getCurrentUserId(mUserManager, mIsWorkProfile);
165     }
166 
167     @Override
isAvailable()168     public boolean isAvailable() {
169         return true;
170     }
171 
172     @Override
handlePreferenceTreeClick(Preference preference)173     public boolean handlePreferenceTreeClick(Preference preference) {
174         if (preference.getKey() == null) {
175             return false;
176         }
177         switch (preference.getKey()) {
178             case PUBLIC_STORAGE_KEY:
179                 launchPublicStorageIntent();
180                 return true;
181             case IMAGES_KEY:
182                 launchActivityWithUri(mImagesUri);
183                 return true;
184             case VIDEOS_KEY:
185                 launchActivityWithUri(mVideosUri);
186                 return true;
187             case AUDIO_KEY:
188                 launchActivityWithUri(mAudioUri);
189                 return true;
190             case APPS_KEY:
191                 launchAppsIntent();
192                 return true;
193             case GAMES_KEY:
194                 launchGamesIntent();
195                 return true;
196             case DOCUMENTS_AND_OTHER_KEY:
197                 launchActivityWithUri(mDocumentsAndOtherUri);
198                 return true;
199             case SYSTEM_KEY:
200                 final SystemInfoFragment dialog = new SystemInfoFragment();
201                 dialog.setTargetFragment(mFragment, 0);
202                 dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG);
203                 return true;
204             case TRASH_KEY:
205                 launchTrashIntent();
206                 return true;
207             default:
208                 // Do nothing.
209         }
210         return super.handlePreferenceTreeClick(preference);
211     }
212 
213     @Override
getPreferenceKey()214     public String getPreferenceKey() {
215         return null;
216     }
217 
218     /**
219      * Sets the storage volume to use for when handling taps.
220      */
setVolume(VolumeInfo volume)221     public void setVolume(VolumeInfo volume) {
222         mVolume = volume;
223 
224         if (mPublicStoragePreference != null) {
225             mPublicStoragePreference.setVisible(isValidPublicVolume());
226         }
227 
228         // If isValidPrivateVolume() is true, these preferences will become visible at
229         // onLoadFinished.
230         if (!isValidPrivateVolume()) {
231             setPrivateStorageCategoryPreferencesVisibility(false);
232         }
233     }
234 
235     // Stats data is only available on private volumes.
isValidPrivateVolume()236     private boolean isValidPrivateVolume() {
237         return mVolume != null
238                 && mVolume.getType() == VolumeInfo.TYPE_PRIVATE
239                 && (mVolume.getState() == VolumeInfo.STATE_MOUNTED
240                 || mVolume.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY);
241     }
242 
isValidPublicVolume()243     private boolean isValidPublicVolume() {
244         // Stub volume is a volume that is maintained by external party such as the ChromeOS
245         // processes in ARC++.
246         return mVolume != null
247                 && (mVolume.getType() == VolumeInfo.TYPE_PUBLIC
248                 || mVolume.getType() == VolumeInfo.TYPE_STUB)
249                 && (mVolume.getState() == VolumeInfo.STATE_MOUNTED
250                 || mVolume.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY);
251     }
252 
253     @VisibleForTesting
setPrivateStorageCategoryPreferencesVisibility(boolean visible)254     void setPrivateStorageCategoryPreferencesVisibility(boolean visible) {
255         if (mScreen == null) {
256             return;
257         }
258 
259         mImagesPreference.setVisible(visible);
260         mVideosPreference.setVisible(visible);
261         mAudioPreference.setVisible(visible);
262         mAppsPreference.setVisible(visible);
263         mGamesPreference.setVisible(visible);
264         mSystemPreference.setVisible(visible);
265         mTrashPreference.setVisible(visible);
266 
267         // If we don't have a shared volume for our internal storage (or the shared volume isn't
268         // mounted as readable for whatever reason), we should hide the File preference.
269         if (visible) {
270             final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
271             mDocumentsAndOtherPreference.setVisible(sharedVolume != null
272                     && sharedVolume.isMountedReadable());
273         } else {
274             mDocumentsAndOtherPreference.setVisible(false);
275         }
276     }
277 
updatePrivateStorageCategoryPreferencesOrder()278     private void updatePrivateStorageCategoryPreferencesOrder() {
279         if (mScreen == null || !isValidPrivateVolume()) {
280             return;
281         }
282 
283         if (mPrivateStorageItemPreferences == null) {
284             mPrivateStorageItemPreferences = new ArrayList<>();
285 
286             mPrivateStorageItemPreferences.add(mImagesPreference);
287             mPrivateStorageItemPreferences.add(mVideosPreference);
288             mPrivateStorageItemPreferences.add(mAudioPreference);
289             mPrivateStorageItemPreferences.add(mAppsPreference);
290             mPrivateStorageItemPreferences.add(mGamesPreference);
291             mPrivateStorageItemPreferences.add(mDocumentsAndOtherPreference);
292             mPrivateStorageItemPreferences.add(mSystemPreference);
293             mPrivateStorageItemPreferences.add(mTrashPreference);
294         }
295         mScreen.removePreference(mImagesPreference);
296         mScreen.removePreference(mVideosPreference);
297         mScreen.removePreference(mAudioPreference);
298         mScreen.removePreference(mAppsPreference);
299         mScreen.removePreference(mGamesPreference);
300         mScreen.removePreference(mDocumentsAndOtherPreference);
301         mScreen.removePreference(mSystemPreference);
302         mScreen.removePreference(mTrashPreference);
303 
304         // Sort display order by size.
305         Collections.sort(mPrivateStorageItemPreferences,
306                 Comparator.comparingLong(StorageItemPreference::getStorageSize));
307         int orderIndex = LAST_STORAGE_CATEGORY_PREFERENCE_ORDER;
308         for (StorageItemPreference preference : mPrivateStorageItemPreferences) {
309             preference.setOrder(orderIndex--);
310             mScreen.addPreference(preference);
311         }
312     }
313 
314     /**
315      * Sets the user id for which this preference controller is handling.
316      */
setUserId(UserHandle userHandle)317     public void setUserId(UserHandle userHandle) {
318         if (mIsWorkProfile && !mUserManager.isManagedProfile(userHandle.getIdentifier())) {
319             throw new IllegalArgumentException("Only accept work profile userHandle");
320         }
321         mUserId = userHandle.getIdentifier();
322 
323         tintPreference(mPublicStoragePreference);
324         tintPreference(mImagesPreference);
325         tintPreference(mVideosPreference);
326         tintPreference(mAudioPreference);
327         tintPreference(mAppsPreference);
328         tintPreference(mGamesPreference);
329         tintPreference(mDocumentsAndOtherPreference);
330         tintPreference(mSystemPreference);
331         tintPreference(mTrashPreference);
332     }
333 
tintPreference(Preference preference)334     private void tintPreference(Preference preference) {
335         if (preference != null) {
336             preference.setIcon(applyTint(mContext, preference.getIcon()));
337         }
338     }
339 
applyTint(Context context, Drawable icon)340     private static Drawable applyTint(Context context, Drawable icon) {
341         TypedArray array =
342                 context.obtainStyledAttributes(new int[]{android.R.attr.colorControlNormal});
343         icon = icon.mutate();
344         icon.setTint(array.getColor(0, 0));
345         array.recycle();
346         return icon;
347     }
348 
349     @Override
displayPreference(PreferenceScreen screen)350     public void displayPreference(PreferenceScreen screen) {
351         mScreen = screen;
352         mPublicStoragePreference = screen.findPreference(PUBLIC_STORAGE_KEY);
353         mImagesPreference = screen.findPreference(IMAGES_KEY);
354         mVideosPreference = screen.findPreference(VIDEOS_KEY);
355         mAudioPreference = screen.findPreference(AUDIO_KEY);
356         mAppsPreference = screen.findPreference(APPS_KEY);
357         mGamesPreference = screen.findPreference(GAMES_KEY);
358         mDocumentsAndOtherPreference = screen.findPreference(DOCUMENTS_AND_OTHER_KEY);
359         mSystemPreference = screen.findPreference(SYSTEM_KEY);
360         mTrashPreference = screen.findPreference(TRASH_KEY);
361     }
362 
363     /** Fragments use it to set storage result and update UI of this controller. */
onLoadFinished(SparseArray<StorageAsyncLoader.StorageResult> result, int userId)364     public void onLoadFinished(SparseArray<StorageAsyncLoader.StorageResult> result, int userId) {
365         final StorageAsyncLoader.StorageResult data = result.get(userId);
366 
367         mImagesPreference.setStorageSize(data.imagesSize, mTotalSize);
368         mVideosPreference.setStorageSize(data.videosSize, mTotalSize);
369         mAudioPreference.setStorageSize(data.audioSize, mTotalSize);
370         mAppsPreference.setStorageSize(data.allAppsExceptGamesSize, mTotalSize);
371         mGamesPreference.setStorageSize(data.gamesSize, mTotalSize);
372         mDocumentsAndOtherPreference.setStorageSize(data.documentsAndOtherSize, mTotalSize);
373         mTrashPreference.setStorageSize(data.trashSize, mTotalSize);
374 
375         if (mSystemPreference != null) {
376             // Everything else that hasn't already been attributed is tracked as
377             // belonging to system.
378             long attributedSize = 0;
379             for (int i = 0; i < result.size(); i++) {
380                 final StorageAsyncLoader.StorageResult otherData = result.valueAt(i);
381                 attributedSize +=
382                         otherData.gamesSize
383                                 + otherData.audioSize
384                                 + otherData.videosSize
385                                 + otherData.imagesSize
386                                 + otherData.documentsAndOtherSize
387                                 + otherData.trashSize
388                                 + otherData.allAppsExceptGamesSize;
389                 attributedSize -= otherData.duplicateCodeSize;
390             }
391 
392             final long systemSize = Math.max(TrafficStats.GB_IN_BYTES, mUsedBytes - attributedSize);
393             mSystemPreference.setStorageSize(systemSize, mTotalSize);
394         }
395 
396         updatePrivateStorageCategoryPreferencesOrder();
397         setPrivateStorageCategoryPreferencesVisibility(true);
398     }
399 
setUsedSize(long usedSizeBytes)400     public void setUsedSize(long usedSizeBytes) {
401         mUsedBytes = usedSizeBytes;
402     }
403 
setTotalSize(long totalSizeBytes)404     public void setTotalSize(long totalSizeBytes) {
405         mTotalSize = totalSizeBytes;
406     }
407 
launchPublicStorageIntent()408     private void launchPublicStorageIntent() {
409         final Intent intent = mVolume.buildBrowseIntent();
410         if (intent == null) {
411             return;
412         }
413         mContext.startActivityAsUser(intent, new UserHandle(mUserId));
414     }
415 
launchActivityWithUri(Uri dataUri)416     private void launchActivityWithUri(Uri dataUri) {
417         final Intent intent = new Intent(Intent.ACTION_VIEW);
418         intent.setData(dataUri);
419         mContext.startActivityAsUser(intent, new UserHandle(mUserId));
420     }
421 
launchAppsIntent()422     private void launchAppsIntent() {
423         final Bundle args = getWorkAnnotatedBundle(3);
424         args.putString(ManageApplications.EXTRA_CLASSNAME,
425                 Settings.StorageUseActivity.class.getName());
426         args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
427         args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
428         final Intent intent = new SubSettingLauncher(mContext)
429                 .setDestination(ManageApplications.class.getName())
430                 .setTitleRes(R.string.apps_storage)
431                 .setArguments(args)
432                 .setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
433                 .toIntent();
434         intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
435         Utils.launchIntent(mFragment, intent);
436     }
437 
launchGamesIntent()438     private void launchGamesIntent() {
439         final Bundle args = getWorkAnnotatedBundle(1);
440         args.putString(ManageApplications.EXTRA_CLASSNAME,
441                 Settings.GamesStorageActivity.class.getName());
442         final Intent intent = new SubSettingLauncher(mContext)
443                 .setDestination(ManageApplications.class.getName())
444                 .setTitleRes(R.string.game_storage_settings)
445                 .setArguments(args)
446                 .setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
447                 .toIntent();
448         intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
449         Utils.launchIntent(mFragment, intent);
450     }
451 
getWorkAnnotatedBundle(int additionalCapacity)452     private Bundle getWorkAnnotatedBundle(int additionalCapacity) {
453         final Bundle args = new Bundle(1 + additionalCapacity);
454         args.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB,
455                 mIsWorkProfile ? WORK_TAB : PERSONAL_TAB);
456         return args;
457     }
458 
launchTrashIntent()459     private void launchTrashIntent() {
460         final Intent intent = new Intent("android.settings.VIEW_TRASH");
461 
462         if (mPackageManager.resolveActivityAsUser(intent, 0 /* flags */, mUserId) == null) {
463             final long trashSize = mTrashPreference.getStorageSize();
464             if (trashSize > 0) {
465                 new EmptyTrashFragment(mFragment, mUserId, trashSize,
466                         this /* onEmptyTrashCompleteListener */).show();
467             } else {
468                 Toast.makeText(mContext, R.string.storage_trash_dialog_empty_message,
469                         Toast.LENGTH_SHORT).show();
470             }
471         } else {
472             mContext.startActivityAsUser(intent, new UserHandle(mUserId));
473         }
474     }
475 
476     @Override
onEmptyTrashComplete()477     public void onEmptyTrashComplete() {
478         if (mTrashPreference == null) {
479             return;
480         }
481         mTrashPreference.setStorageSize(0, mTotalSize);
482         updatePrivateStorageCategoryPreferencesOrder();
483     }
484 
totalValues(StorageMeasurement.MeasurementDetails details, int userId, String... keys)485     private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId,
486             String... keys) {
487         long total = 0;
488         Map<String, Long> map = details.mediaSize.get(userId);
489         if (map != null) {
490             for (String key : keys) {
491                 if (map.containsKey(key)) {
492                     total += map.get(key);
493                 }
494             }
495         } else {
496             Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
497         }
498         return total;
499     }
500 }
501