• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settings.deviceinfo;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.content.ActivityNotFoundException;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.IPackageDataObserver;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.UserInfo;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.SystemProperties;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.os.storage.StorageEventListener;
37 import android.os.storage.StorageManager;
38 import android.os.storage.VolumeInfo;
39 import android.os.storage.VolumeRecord;
40 import android.provider.DocumentsContract;
41 import android.provider.Settings;
42 import android.support.v7.preference.Preference;
43 import android.support.v7.preference.PreferenceCategory;
44 import android.support.v7.preference.PreferenceGroup;
45 import android.support.v7.preference.PreferenceScreen;
46 import android.text.TextUtils;
47 import android.text.format.Formatter;
48 import android.text.format.Formatter.BytesResult;
49 import android.util.Log;
50 import android.view.LayoutInflater;
51 import android.view.Menu;
52 import android.view.MenuInflater;
53 import android.view.MenuItem;
54 import android.view.View;
55 import android.widget.EditText;
56 
57 import com.android.internal.logging.MetricsProto.MetricsEvent;
58 import com.android.settings.R;
59 import com.android.settings.Settings.StorageUseActivity;
60 import com.android.settings.SettingsPreferenceFragment;
61 import com.android.settings.Utils;
62 import com.android.settings.applications.ManageApplications;
63 import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
64 import com.android.settings.deviceinfo.StorageSettings.MountTask;
65 import com.android.settingslib.deviceinfo.StorageMeasurement;
66 import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
67 import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementReceiver;
68 import com.google.android.collect.Lists;
69 
70 import java.io.File;
71 import java.util.HashMap;
72 import java.util.List;
73 import java.util.Objects;
74 
75 import static com.android.settings.deviceinfo.StorageSettings.TAG;
76 
77 /**
78  * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE}
79  * storage volume.
80  */
81 public class PrivateVolumeSettings extends SettingsPreferenceFragment {
82     // TODO: disable unmount when providing over MTP/PTP
83     // TODO: warn when mounted read-only
84 
85     private static final String TAG_RENAME = "rename";
86     private static final String TAG_OTHER_INFO = "otherInfo";
87     private static final String TAG_SYSTEM_INFO = "systemInfo";
88     private static final String TAG_USER_INFO = "userInfo";
89     private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
90 
91     private static final String EXTRA_VOLUME_SIZE = "volume_size";
92 
93     private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
94 
95     private static final String STORAGE_MANAGER_PROPERTY = "ro.storage_manager.enabled";
96 
97     private static final int[] ITEMS_NO_SHOW_SHARED = new int[] {
98             R.string.storage_detail_apps,
99             R.string.storage_detail_system,
100     };
101 
102     private static final int[] ITEMS_SHOW_SHARED = new int[] {
103             R.string.storage_detail_apps,
104             R.string.storage_detail_images,
105             R.string.storage_detail_videos,
106             R.string.storage_detail_audio,
107             R.string.storage_detail_system,
108             R.string.storage_detail_other,
109     };
110 
111     private static final int DELETION_HELPER_SETTINGS = 1;
112     private static final int DELETION_HELPER_CLEAR = 1;
113 
114     private StorageManager mStorageManager;
115     private UserManager mUserManager;
116 
117     private String mVolumeId;
118     private VolumeInfo mVolume;
119     private VolumeInfo mSharedVolume;
120     private long mTotalSize;
121     private long mSystemSize;
122 
123     private StorageMeasurement mMeasure;
124 
125     private UserInfo mCurrentUser;
126 
127     private StorageSummaryPreference mSummary;
128     private List<StorageItemPreference> mItemPreferencePool = Lists.newArrayList();
129     private List<PreferenceCategory> mHeaderPreferencePool = Lists.newArrayList();
130     private int mHeaderPoolIndex;
131     private int mItemPoolIndex;
132 
133     private Preference mExplore;
134     private Preference mAutomaticStorageManagement;
135 
136     private boolean mNeedsUpdate;
137 
isVolumeValid()138     private boolean isVolumeValid() {
139         return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
140                 && mVolume.isMountedReadable();
141     }
142 
PrivateVolumeSettings()143     public PrivateVolumeSettings() {
144         setRetainInstance(true);
145     }
146 
147     @Override
getMetricsCategory()148     protected int getMetricsCategory() {
149         return MetricsEvent.DEVICEINFO_STORAGE;
150     }
151 
152     @Override
onCreate(Bundle icicle)153     public void onCreate(Bundle icicle) {
154         super.onCreate(icicle);
155 
156         final Context context = getActivity();
157 
158         mUserManager = context.getSystemService(UserManager.class);
159         mStorageManager = context.getSystemService(StorageManager.class);
160 
161         mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
162         mVolume = mStorageManager.findVolumeById(mVolumeId);
163 
164         final long sharedDataSize = mVolume.getPath().getTotalSpace();
165         mTotalSize = getArguments().getLong(EXTRA_VOLUME_SIZE, 0);
166         mSystemSize = mTotalSize - sharedDataSize;
167         if (mTotalSize <= 0) {
168             mTotalSize = sharedDataSize;
169             mSystemSize = 0;
170         }
171 
172         // Find the emulated shared storage layered above this private volume
173         mSharedVolume = mStorageManager.findEmulatedForPrivate(mVolume);
174 
175         mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume);
176         mMeasure.setReceiver(mReceiver);
177 
178         if (!isVolumeValid()) {
179             getActivity().finish();
180             return;
181         }
182 
183         addPreferencesFromResource(R.xml.device_info_storage_volume);
184         getPreferenceScreen().setOrderingAsAdded(true);
185 
186         mSummary = new StorageSummaryPreference(getPrefContext());
187         mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId());
188 
189         mExplore = buildAction(R.string.storage_menu_explore);
190         mAutomaticStorageManagement = buildAction(R.string.storage_menu_manage);
191 
192         mNeedsUpdate = true;
193 
194         setHasOptionsMenu(true);
195     }
196 
setTitle()197     private void setTitle() {
198         getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume));
199     }
200 
update()201     private void update() {
202         if (!isVolumeValid()) {
203             getActivity().finish();
204             return;
205         }
206 
207         setTitle();
208 
209         // Valid options may have changed
210         getFragmentManager().invalidateOptionsMenu();
211 
212         final Context context = getActivity();
213         final PreferenceScreen screen = getPreferenceScreen();
214 
215         screen.removeAll();
216 
217         if (SystemProperties.getBoolean(STORAGE_MANAGER_PROPERTY, false)) {
218             addPreference(screen, mAutomaticStorageManagement);
219         }
220         addPreference(screen, mSummary);
221 
222         List<UserInfo> allUsers = mUserManager.getUsers();
223         final int userCount = allUsers.size();
224         final boolean showHeaders = userCount > 1;
225         final boolean showShared = (mSharedVolume != null) && mSharedVolume.isMountedReadable();
226 
227         mItemPoolIndex = 0;
228         mHeaderPoolIndex = 0;
229 
230         int addedUserCount = 0;
231         // Add current user and its profiles first
232         for (int userIndex = 0; userIndex < userCount; ++userIndex) {
233             final UserInfo userInfo = allUsers.get(userIndex);
234             if (isProfileOf(mCurrentUser, userInfo)) {
235                 final PreferenceGroup details = showHeaders ?
236                         addCategory(screen, userInfo.name) : screen;
237                 addDetailItems(details, showShared, userInfo.id);
238                 ++addedUserCount;
239             }
240         }
241 
242         // Add rest of users
243         if (userCount - addedUserCount > 0) {
244             PreferenceGroup otherUsers = addCategory(screen,
245                     getText(R.string.storage_other_users));
246             for (int userIndex = 0; userIndex < userCount; ++userIndex) {
247                 final UserInfo userInfo = allUsers.get(userIndex);
248                 if (!isProfileOf(mCurrentUser, userInfo)) {
249                     addItem(otherUsers, /* titleRes */ 0, userInfo.name, userInfo.id);
250                 }
251             }
252         }
253 
254         addItem(screen, R.string.storage_detail_cached, null, UserHandle.USER_NULL);
255 
256         if (showShared) {
257             addPreference(screen, mExplore);
258         }
259 
260         final long freeBytes = mVolume.getPath().getFreeSpace();
261         final long usedBytes = mTotalSize - freeBytes;
262 
263         final BytesResult result = Formatter.formatBytes(getResources(), usedBytes, 0);
264         mSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
265                 result.value, result.units));
266         mSummary.setSummary(getString(R.string.storage_volume_used,
267                 Formatter.formatFileSize(context, mTotalSize)));
268         mSummary.setPercent((int) ((usedBytes * 100) / mTotalSize));
269 
270         mMeasure.forceMeasure();
271         mNeedsUpdate = false;
272     }
273 
addPreference(PreferenceGroup group, Preference pref)274     private void addPreference(PreferenceGroup group, Preference pref) {
275         pref.setOrder(Preference.DEFAULT_ORDER);
276         group.addPreference(pref);
277     }
278 
addCategory(PreferenceGroup group, CharSequence title)279     private PreferenceCategory addCategory(PreferenceGroup group, CharSequence title) {
280         PreferenceCategory category;
281         if (mHeaderPoolIndex < mHeaderPreferencePool.size()) {
282             category = mHeaderPreferencePool.get(mHeaderPoolIndex);
283         } else {
284             category = new PreferenceCategory(getPrefContext(), null,
285                     com.android.internal.R.attr.preferenceCategoryStyle);
286             mHeaderPreferencePool.add(category);
287         }
288         category.setTitle(title);
289         category.removeAll();
290         addPreference(group, category);
291         ++mHeaderPoolIndex;
292         return category;
293     }
294 
addDetailItems(PreferenceGroup category, boolean showShared, int userId)295     private void addDetailItems(PreferenceGroup category, boolean showShared, int userId) {
296         final int[] itemsToAdd = (showShared ? ITEMS_SHOW_SHARED : ITEMS_NO_SHOW_SHARED);
297         for (int i = 0; i < itemsToAdd.length; ++i) {
298             addItem(category, itemsToAdd[i], null, userId);
299         }
300     }
301 
addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId)302     private void addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId) {
303         if (titleRes == R.string.storage_detail_system && mSystemSize <= 0) {
304             Log.w(TAG, "Skipping System storage because its size is " + mSystemSize);
305             return;
306         }
307         StorageItemPreference item;
308         if (mItemPoolIndex < mItemPreferencePool.size()) {
309             item = mItemPreferencePool.get(mItemPoolIndex);
310         } else {
311             item = buildItem();
312             mItemPreferencePool.add(item);
313         }
314         if (title != null) {
315             item.setTitle(title);
316             item.setKey(title.toString());
317         } else {
318             item.setTitle(titleRes);
319             item.setKey(Integer.toString(titleRes));
320         }
321         item.setSummary(R.string.memory_calculating_size);
322         item.userHandle = userId;
323         addPreference(group, item);
324         ++mItemPoolIndex;
325     }
326 
buildItem()327     private StorageItemPreference buildItem() {
328         final StorageItemPreference item = new StorageItemPreference(getPrefContext());
329         return item;
330     }
331 
buildAction(int titleRes)332     private Preference buildAction(int titleRes) {
333         final Preference pref = new Preference(getPrefContext());
334         pref.setTitle(titleRes);
335         pref.setKey(Integer.toString(titleRes));
336         return pref;
337     }
338 
setVolumeSize(Bundle args, long size)339     static void setVolumeSize(Bundle args, long size) {
340         args.putLong(EXTRA_VOLUME_SIZE, size);
341     }
342 
343     @Override
onResume()344     public void onResume() {
345         super.onResume();
346 
347         // Refresh to verify that we haven't been formatted away
348         mVolume = mStorageManager.findVolumeById(mVolumeId);
349         if (!isVolumeValid()) {
350             getActivity().finish();
351             return;
352         }
353 
354         mStorageManager.registerListener(mStorageListener);
355 
356         if (mNeedsUpdate) {
357             update();
358         } else {
359             setTitle();
360         }
361     }
362 
363     @Override
onPause()364     public void onPause() {
365         super.onPause();
366         mStorageManager.unregisterListener(mStorageListener);
367     }
368 
369     @Override
onDestroy()370     public void onDestroy() {
371         super.onDestroy();
372         if (mMeasure != null) {
373             mMeasure.onDestroy();
374         }
375     }
376 
377     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)378     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
379         super.onCreateOptionsMenu(menu, inflater);
380         inflater.inflate(R.menu.storage_volume, menu);
381     }
382 
383     @Override
onPrepareOptionsMenu(Menu menu)384     public void onPrepareOptionsMenu(Menu menu) {
385         if (!isVolumeValid()) return;
386 
387         final MenuItem rename = menu.findItem(R.id.storage_rename);
388         final MenuItem mount = menu.findItem(R.id.storage_mount);
389         final MenuItem unmount = menu.findItem(R.id.storage_unmount);
390         final MenuItem format = menu.findItem(R.id.storage_format);
391         final MenuItem migrate = menu.findItem(R.id.storage_migrate);
392         final MenuItem manage = menu.findItem(R.id.storage_free);
393 
394         // Actions live in menu for non-internal private volumes; they're shown
395         // as preference items for public volumes.
396         if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.getId())) {
397             rename.setVisible(false);
398             mount.setVisible(false);
399             unmount.setVisible(false);
400             format.setVisible(false);
401             manage.setVisible(SystemProperties.getBoolean(STORAGE_MANAGER_PROPERTY, false));
402         } else {
403             rename.setVisible(mVolume.getType() == VolumeInfo.TYPE_PRIVATE);
404             mount.setVisible(mVolume.getState() == VolumeInfo.STATE_UNMOUNTED);
405             unmount.setVisible(mVolume.isMountedReadable());
406             format.setVisible(true);
407             manage.setVisible(false);
408         }
409 
410         format.setTitle(R.string.storage_menu_format_public);
411 
412         // Only offer to migrate when not current storage
413         final VolumeInfo privateVol = getActivity().getPackageManager()
414                 .getPrimaryStorageCurrentVolume();
415         migrate.setVisible((privateVol != null)
416                 && (privateVol.getType() == VolumeInfo.TYPE_PRIVATE)
417                 && !Objects.equals(mVolume, privateVol));
418     }
419 
420     @Override
onOptionsItemSelected(MenuItem item)421     public boolean onOptionsItemSelected(MenuItem item) {
422         final Context context = getActivity();
423         final Bundle args = new Bundle();
424         switch (item.getItemId()) {
425             case R.id.storage_rename:
426                 RenameFragment.show(this, mVolume);
427                 return true;
428             case R.id.storage_mount:
429                 new MountTask(context, mVolume).execute();
430                 return true;
431             case R.id.storage_unmount:
432                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
433                 startFragment(this, PrivateVolumeUnmount.class.getCanonicalName(),
434                         R.string.storage_menu_unmount, 0, args);
435                 return true;
436             case R.id.storage_format:
437                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
438                 startFragment(this, PrivateVolumeFormat.class.getCanonicalName(),
439                         R.string.storage_menu_format, 0, args);
440                 return true;
441             case R.id.storage_migrate:
442                 final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class);
443                 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
444                 startActivity(intent);
445                 return true;
446             case R.id.storage_free:
447                 final Intent deletion_helper_intent =
448                         new Intent(StorageManager.ACTION_MANAGE_STORAGE);
449                 startActivity(deletion_helper_intent);
450                 return true;
451         }
452         return super.onOptionsItemSelected(item);
453     }
454 
455     @Override
onPreferenceTreeClick(Preference pref)456     public boolean onPreferenceTreeClick(Preference pref) {
457         // TODO: launch better intents for specific volume
458 
459         final int userId = (pref instanceof StorageItemPreference ?
460                 ((StorageItemPreference)pref).userHandle : -1);
461         int itemTitleId;
462         try {
463             itemTitleId = Integer.parseInt(pref.getKey());
464         } catch (NumberFormatException e) {
465             itemTitleId = 0;
466         }
467         Intent intent = null;
468         switch (itemTitleId) {
469             case R.string.storage_detail_apps: {
470                 Bundle args = new Bundle();
471                 args.putString(ManageApplications.EXTRA_CLASSNAME,
472                         StorageUseActivity.class.getName());
473                 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
474                 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
475                 intent = Utils.onBuildStartFragmentIntent(getActivity(),
476                         ManageApplications.class.getName(), args, null, R.string.apps_storage, null,
477                         false);
478 
479             } break;
480             case R.string.storage_detail_images: {
481                 intent = new Intent(DocumentsContract.ACTION_BROWSE);
482                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root"));
483                 intent.addCategory(Intent.CATEGORY_DEFAULT);
484 
485             } break;
486             case R.string.storage_detail_videos: {
487                 intent = new Intent(DocumentsContract.ACTION_BROWSE);
488                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "videos_root"));
489                 intent.addCategory(Intent.CATEGORY_DEFAULT);
490 
491             } break;
492             case R.string.storage_detail_audio: {
493                 intent = new Intent(DocumentsContract.ACTION_BROWSE);
494                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
495                 intent.addCategory(Intent.CATEGORY_DEFAULT);
496 
497             } break;
498             case R.string.storage_detail_system: {
499                 SystemInfoFragment.show(this);
500                 return true;
501 
502             }
503             case R.string.storage_detail_other: {
504                 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
505                         mSharedVolume);
506                 return true;
507 
508             }
509             case R.string.storage_detail_cached: {
510                 ConfirmClearCacheFragment.show(this);
511                 return true;
512 
513             }
514             case R.string.storage_menu_explore: {
515                 intent = mSharedVolume.buildBrowseIntent();
516             } break;
517             case R.string.storage_menu_manage: {
518                 startFragment(this, AutomaticStorageManagerSettings.class.getCanonicalName(),
519                         R.string.automatic_storage_manager_settings, 0, null);
520                 return true;
521             }
522             case 0: {
523                 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
524                 return true;
525             }
526         }
527 
528         if (intent != null) {
529             try {
530                 if (userId == -1) {
531                     startActivity(intent);
532                 } else {
533                     getActivity().startActivityAsUser(intent, new UserHandle(userId));
534                 }
535             } catch (ActivityNotFoundException e) {
536                 Log.w(TAG, "No activity found for " + intent);
537             }
538             return true;
539         }
540         return super.onPreferenceTreeClick(pref);
541     }
542 
543     private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
544         @Override
545         public void onDetailsChanged(MeasurementDetails details) {
546             updateDetails(details);
547         }
548     };
549 
updateDetails(MeasurementDetails details)550     private void updateDetails(MeasurementDetails details) {
551         for (int i = 0; i < mItemPoolIndex; ++i) {
552             StorageItemPreference item = mItemPreferencePool.get(i);
553             final int userId = item.userHandle;
554             int itemTitleId;
555             try {
556                 itemTitleId = Integer.parseInt(item.getKey());
557             } catch (NumberFormatException e) {
558                 itemTitleId = 0;
559             }
560             switch (itemTitleId) {
561                 case R.string.storage_detail_system: {
562                     updatePreference(item, mSystemSize);
563                 } break;
564                 case R.string.storage_detail_apps: {
565                     updatePreference(item, details.appsSize.get(userId));
566                 } break;
567                 case R.string.storage_detail_images: {
568                     final long imagesSize = totalValues(details, userId,
569                             Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
570                             Environment.DIRECTORY_PICTURES);
571                     updatePreference(item, imagesSize);
572                 } break;
573                 case R.string.storage_detail_videos: {
574                     final long videosSize = totalValues(details, userId,
575                             Environment.DIRECTORY_MOVIES);
576                     updatePreference(item, videosSize);
577                 } break;
578                 case R.string.storage_detail_audio: {
579                     final long audioSize = totalValues(details, userId,
580                             Environment.DIRECTORY_MUSIC,
581                             Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
582                             Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
583                     updatePreference(item, audioSize);
584                 } break;
585                 case R.string.storage_detail_other: {
586                     updatePreference(item, details.miscSize.get(userId));
587                 } break;
588                 case R.string.storage_detail_cached: {
589                     updatePreference(item, details.cacheSize);
590                 } break;
591                 case 0: {
592                     final long userSize = details.usersSize.get(userId);
593                     updatePreference(item, userSize);
594                 } break;
595             }
596         }
597     }
598 
updatePreference(StorageItemPreference pref, long size)599     private void updatePreference(StorageItemPreference pref, long size) {
600         pref.setStorageSize(size, mTotalSize);
601     }
602 
isProfileOf(UserInfo user, UserInfo profile)603     private boolean isProfileOf(UserInfo user, UserInfo profile) {
604         return user.id == profile.id ||
605                 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
606                 && user.profileGroupId == profile.profileGroupId);
607     }
608 
totalValues(MeasurementDetails details, int userId, String... keys)609     private static long totalValues(MeasurementDetails details, int userId, String... keys) {
610         long total = 0;
611         HashMap<String, Long> map = details.mediaSize.get(userId);
612         if (map != null) {
613             for (String key : keys) {
614                 if (map.containsKey(key)) {
615                     total += map.get(key);
616                 }
617             }
618         } else {
619             Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
620         }
621         return total;
622     }
623 
624     private final StorageEventListener mStorageListener = new StorageEventListener() {
625         @Override
626         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
627             if (Objects.equals(mVolume.getId(), vol.getId())) {
628                 mVolume = vol;
629                 update();
630             }
631         }
632 
633         @Override
634         public void onVolumeRecordChanged(VolumeRecord rec) {
635             if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
636                 mVolume = mStorageManager.findVolumeById(mVolumeId);
637                 update();
638             }
639         }
640     };
641 
642     /**
643      * Dialog that allows editing of volume nickname.
644      */
645     public static class RenameFragment extends DialogFragment {
show(PrivateVolumeSettings parent, VolumeInfo vol)646         public static void show(PrivateVolumeSettings parent, VolumeInfo vol) {
647             if (!parent.isAdded()) return;
648 
649             final RenameFragment dialog = new RenameFragment();
650             dialog.setTargetFragment(parent, 0);
651             final Bundle args = new Bundle();
652             args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
653             dialog.setArguments(args);
654             dialog.show(parent.getFragmentManager(), TAG_RENAME);
655         }
656 
657         @Override
onCreateDialog(Bundle savedInstanceState)658         public Dialog onCreateDialog(Bundle savedInstanceState) {
659             final Context context = getActivity();
660             final StorageManager storageManager = context.getSystemService(StorageManager.class);
661 
662             final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
663             final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid);
664             final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid);
665 
666             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
667             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
668 
669             final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false);
670             final EditText nickname = (EditText) view.findViewById(R.id.edittext);
671             nickname.setText(rec.getNickname());
672 
673             builder.setTitle(R.string.storage_rename_title);
674             builder.setView(view);
675 
676             builder.setPositiveButton(R.string.save,
677                     new DialogInterface.OnClickListener() {
678                         @Override
679                         public void onClick(DialogInterface dialog, int which) {
680                             // TODO: move to background thread
681                             storageManager.setVolumeNickname(fsUuid,
682                                     nickname.getText().toString());
683                         }
684                     });
685             builder.setNegativeButton(R.string.cancel, null);
686 
687             return builder.create();
688         }
689     }
690 
691     public static class SystemInfoFragment extends DialogFragment {
show(Fragment parent)692         public static void show(Fragment parent) {
693             if (!parent.isAdded()) return;
694 
695             final SystemInfoFragment dialog = new SystemInfoFragment();
696             dialog.setTargetFragment(parent, 0);
697             dialog.show(parent.getFragmentManager(), TAG_SYSTEM_INFO);
698         }
699 
700         @Override
onCreateDialog(Bundle savedInstanceState)701         public Dialog onCreateDialog(Bundle savedInstanceState) {
702             return new AlertDialog.Builder(getActivity())
703                     .setMessage(R.string.storage_detail_dialog_system)
704                     .setPositiveButton(android.R.string.ok, null)
705                     .create();
706         }
707     }
708 
709     public static class OtherInfoFragment extends DialogFragment {
show(Fragment parent, String title, VolumeInfo sharedVol)710         public static void show(Fragment parent, String title, VolumeInfo sharedVol) {
711             if (!parent.isAdded()) return;
712 
713             final OtherInfoFragment dialog = new OtherInfoFragment();
714             dialog.setTargetFragment(parent, 0);
715             final Bundle args = new Bundle();
716             args.putString(Intent.EXTRA_TITLE, title);
717             args.putParcelable(Intent.EXTRA_INTENT, sharedVol.buildBrowseIntent());
718             dialog.setArguments(args);
719             dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO);
720         }
721 
722         @Override
onCreateDialog(Bundle savedInstanceState)723         public Dialog onCreateDialog(Bundle savedInstanceState) {
724             final Context context = getActivity();
725 
726             final String title = getArguments().getString(Intent.EXTRA_TITLE);
727             final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT);
728 
729             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
730             builder.setMessage(
731                     TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title));
732 
733             builder.setPositiveButton(R.string.storage_menu_explore,
734                     new DialogInterface.OnClickListener() {
735                         @Override
736                         public void onClick(DialogInterface dialog, int which) {
737                             startActivity(intent);
738                         }
739                     });
740             builder.setNegativeButton(android.R.string.cancel, null);
741 
742             return builder.create();
743         }
744     }
745 
746     public static class UserInfoFragment extends DialogFragment {
show(Fragment parent, CharSequence userLabel, CharSequence userSize)747         public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) {
748             if (!parent.isAdded()) return;
749 
750             final UserInfoFragment dialog = new UserInfoFragment();
751             dialog.setTargetFragment(parent, 0);
752             final Bundle args = new Bundle();
753             args.putCharSequence(Intent.EXTRA_TITLE, userLabel);
754             args.putCharSequence(Intent.EXTRA_SUBJECT, userSize);
755             dialog.setArguments(args);
756             dialog.show(parent.getFragmentManager(), TAG_USER_INFO);
757         }
758 
759         @Override
onCreateDialog(Bundle savedInstanceState)760         public Dialog onCreateDialog(Bundle savedInstanceState) {
761             final Context context = getActivity();
762 
763             final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE);
764             final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT);
765 
766             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
767             builder.setMessage(TextUtils.expandTemplate(
768                     getText(R.string.storage_detail_dialog_user), userLabel, userSize));
769 
770             builder.setPositiveButton(android.R.string.ok, null);
771 
772             return builder.create();
773         }
774     }
775 
776     /**
777      * Dialog to request user confirmation before clearing all cache data.
778      */
779     public static class ConfirmClearCacheFragment extends DialogFragment {
show(Fragment parent)780         public static void show(Fragment parent) {
781             if (!parent.isAdded()) return;
782 
783             final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
784             dialog.setTargetFragment(parent, 0);
785             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
786         }
787 
788         @Override
onCreateDialog(Bundle savedInstanceState)789         public Dialog onCreateDialog(Bundle savedInstanceState) {
790             final Context context = getActivity();
791 
792             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
793             builder.setTitle(R.string.memory_clear_cache_title);
794             builder.setMessage(getString(R.string.memory_clear_cache_message));
795 
796             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
797                 @Override
798                 public void onClick(DialogInterface dialog, int which) {
799                     final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment();
800                     final PackageManager pm = context.getPackageManager();
801                     final UserManager um = context.getSystemService(UserManager.class);
802 
803                     for (int userId : um.getProfileIdsWithDisabled(context.getUserId())) {
804                         final List<PackageInfo> infos = pm.getInstalledPackagesAsUser(0, userId);
805                         final ClearCacheObserver observer = new ClearCacheObserver(
806                                 target, infos.size());
807                         for (PackageInfo info : infos) {
808                             pm.deleteApplicationCacheFilesAsUser(info.packageName, userId,
809                                     observer);
810                         }
811                     }
812                 }
813             });
814             builder.setNegativeButton(android.R.string.cancel, null);
815 
816             return builder.create();
817         }
818     }
819 
820     private static class ClearCacheObserver extends IPackageDataObserver.Stub {
821         private final PrivateVolumeSettings mTarget;
822         private int mRemaining;
823 
ClearCacheObserver(PrivateVolumeSettings target, int remaining)824         public ClearCacheObserver(PrivateVolumeSettings target, int remaining) {
825             mTarget = target;
826             mRemaining = remaining;
827         }
828 
829         @Override
onRemoveCompleted(final String packageName, final boolean succeeded)830         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
831             synchronized (this) {
832                 if (--mRemaining == 0) {
833                     mTarget.getActivity().runOnUiThread(new Runnable() {
834                         @Override
835                         public void run() {
836                             mTarget.update();
837                         }
838                     });
839                 }
840             }
841         }
842     }
843 }
844