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