OSDN Git Service

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