OSDN Git Service

Fix Help menu for PrivateVolumeSettings
[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         super.onCreateOptionsMenu(menu, inflater);
325         inflater.inflate(R.menu.storage_volume, menu);
326     }
327
328     @Override
329     public void onPrepareOptionsMenu(Menu menu) {
330         if (!isVolumeValid()) return;
331
332         final MenuItem rename = menu.findItem(R.id.storage_rename);
333         final MenuItem mount = menu.findItem(R.id.storage_mount);
334         final MenuItem unmount = menu.findItem(R.id.storage_unmount);
335         final MenuItem format = menu.findItem(R.id.storage_format);
336         final MenuItem migrate = menu.findItem(R.id.storage_migrate);
337
338         // Actions live in menu for non-internal private volumes; they're shown
339         // as preference items for public volumes.
340         if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.getId())) {
341             rename.setVisible(false);
342             mount.setVisible(false);
343             unmount.setVisible(false);
344             format.setVisible(false);
345         } else {
346             rename.setVisible(mVolume.getType() == VolumeInfo.TYPE_PRIVATE);
347             mount.setVisible(mVolume.getState() == VolumeInfo.STATE_UNMOUNTED);
348             unmount.setVisible(mVolume.isMountedReadable());
349             format.setVisible(true);
350         }
351
352         format.setTitle(R.string.storage_menu_format_public);
353
354         // Only offer to migrate when not current storage
355         final VolumeInfo privateVol = getActivity().getPackageManager()
356                 .getPrimaryStorageCurrentVolume();
357         migrate.setVisible(!Objects.equals(mVolume, privateVol));
358     }
359
360     @Override
361     public boolean onOptionsItemSelected(MenuItem item) {
362         final Context context = getActivity();
363         final Bundle args = new Bundle();
364         switch (item.getItemId()) {
365             case R.id.storage_rename:
366                 RenameFragment.show(this, mVolume);
367                 return true;
368             case R.id.storage_mount:
369                 new MountTask(context, mVolume).execute();
370                 return true;
371             case R.id.storage_unmount:
372                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
373                 startFragment(this, PrivateVolumeUnmount.class.getCanonicalName(),
374                         R.string.storage_menu_unmount, 0, args);
375                 return true;
376             case R.id.storage_format:
377                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
378                 startFragment(this, PrivateVolumeFormat.class.getCanonicalName(),
379                         R.string.storage_menu_format, 0, args);
380                 return true;
381             case R.id.storage_migrate:
382                 final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class);
383                 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
384                 startActivity(intent);
385                 return true;
386         }
387         return super.onOptionsItemSelected(item);
388     }
389
390     @Override
391     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) {
392         // TODO: launch better intents for specific volume
393
394         final int userId = (pref instanceof StorageItemPreference ?
395                 ((StorageItemPreference)pref).userHandle : -1);
396         final int itemTitleId = pref.getTitleRes();
397         Intent intent = null;
398         switch (itemTitleId) {
399             case R.string.storage_detail_apps: {
400                 Bundle args = new Bundle();
401                 args.putString(ManageApplications.EXTRA_CLASSNAME,
402                         StorageUseActivity.class.getName());
403                 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
404                 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
405                 intent = Utils.onBuildStartFragmentIntent(getActivity(),
406                         ManageApplications.class.getName(), args, null, R.string.apps_storage, null,
407                         false);
408
409             } break;
410             case R.string.storage_detail_images: {
411                 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
412                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root"));
413                 intent.addCategory(Intent.CATEGORY_DEFAULT);
414
415             } break;
416             case R.string.storage_detail_videos: {
417                 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
418                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "videos_root"));
419                 intent.addCategory(Intent.CATEGORY_DEFAULT);
420
421             } break;
422             case R.string.storage_detail_audio: {
423                 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
424                 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
425                 intent.addCategory(Intent.CATEGORY_DEFAULT);
426
427             } break;
428             case R.string.storage_detail_other: {
429                 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
430                         mSharedVolume);
431                 return true;
432
433             }
434             case R.string.storage_detail_cached: {
435                 ConfirmClearCacheFragment.show(this);
436                 return true;
437
438             }
439             case R.string.storage_menu_explore: {
440                 intent = mSharedVolume.buildBrowseIntent();
441             } break;
442             case 0: {
443                 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
444                 return true;
445             }
446         }
447
448         if (intent != null) {
449             try {
450                 if (userId == -1) {
451                     startActivity(intent);
452                 } else {
453                     getActivity().startActivityAsUser(intent, new UserHandle(userId));
454                 }
455             } catch (ActivityNotFoundException e) {
456                 Log.w(TAG, "No activity found for " + intent);
457             }
458             return true;
459         }
460         return super.onPreferenceTreeClick(preferenceScreen, pref);
461     }
462
463     private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
464         @Override
465         public void onDetailsChanged(MeasurementDetails details) {
466             updateDetails(details);
467         }
468     };
469
470     private void updateDetails(MeasurementDetails details) {
471         for (int i = 0; i < mItemPoolIndex; ++i) {
472             StorageItemPreference item = mItemPreferencePool.get(i);
473             final int userId = item.userHandle;
474             final int itemTitleId = item.getTitleRes();
475             switch (itemTitleId) {
476                 case R.string.storage_detail_apps: {
477                     updatePreference(item, details.appsSize.get(userId));
478                 } break;
479                 case R.string.storage_detail_images: {
480                     final long imagesSize = totalValues(details, userId,
481                             Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
482                             Environment.DIRECTORY_PICTURES);
483                     updatePreference(item, imagesSize);
484                 } break;
485                 case R.string.storage_detail_videos: {
486                     final long videosSize = totalValues(details, userId,
487                             Environment.DIRECTORY_MOVIES);
488                     updatePreference(item, videosSize);
489                 } break;
490                 case R.string.storage_detail_audio: {
491                     final long audioSize = totalValues(details, userId,
492                             Environment.DIRECTORY_MUSIC,
493                             Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
494                             Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
495                     updatePreference(item, audioSize);
496                 } break;
497                 case R.string.storage_detail_other: {
498                     updatePreference(item, details.miscSize.get(userId));
499                 } break;
500                 case R.string.storage_detail_cached: {
501                     updatePreference(item, details.cacheSize);
502                 } break;
503                 case 0: {
504                     final long userSize = details.usersSize.get(userId);
505                     updatePreference(item, userSize);
506                 } break;
507             }
508         }
509     }
510
511     private void updatePreference(StorageItemPreference pref, long size) {
512         pref.setSummary(Formatter.formatFileSize(getActivity(), size));
513     }
514
515     private boolean isProfileOf(UserInfo user, UserInfo profile) {
516         return user.id == profile.id ||
517                 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
518                 && user.profileGroupId == profile.profileGroupId);
519     }
520
521     private static long totalValues(MeasurementDetails details, int userId, String... keys) {
522         long total = 0;
523         HashMap<String, Long> map = details.mediaSize.get(userId);
524         if (map != null) {
525             for (String key : keys) {
526                 if (map.containsKey(key)) {
527                     total += map.get(key);
528                 }
529             }
530         } else {
531             Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
532         }
533         return total;
534     }
535
536     private final StorageEventListener mStorageListener = new StorageEventListener() {
537         @Override
538         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
539             if (Objects.equals(mVolume.getId(), vol.getId())) {
540                 mVolume = vol;
541                 update();
542             }
543         }
544
545         @Override
546         public void onVolumeRecordChanged(VolumeRecord rec) {
547             if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
548                 mVolume = mStorageManager.findVolumeById(mVolumeId);
549                 update();
550             }
551         }
552     };
553
554     /**
555      * Dialog that allows editing of volume nickname.
556      */
557     public static class RenameFragment extends DialogFragment {
558         public static void show(PrivateVolumeSettings parent, VolumeInfo vol) {
559             if (!parent.isAdded()) return;
560
561             final RenameFragment dialog = new RenameFragment();
562             dialog.setTargetFragment(parent, 0);
563             final Bundle args = new Bundle();
564             args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
565             dialog.setArguments(args);
566             dialog.show(parent.getFragmentManager(), TAG_RENAME);
567         }
568
569         @Override
570         public Dialog onCreateDialog(Bundle savedInstanceState) {
571             final Context context = getActivity();
572             final StorageManager storageManager = context.getSystemService(StorageManager.class);
573
574             final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
575             final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid);
576             final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid);
577
578             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
579             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
580
581             final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false);
582             final EditText nickname = (EditText) view.findViewById(R.id.edittext);
583             nickname.setText(rec.getNickname());
584
585             builder.setTitle(R.string.storage_rename_title);
586             builder.setView(view);
587
588             builder.setPositiveButton(R.string.save,
589                     new DialogInterface.OnClickListener() {
590                         @Override
591                         public void onClick(DialogInterface dialog, int which) {
592                             // TODO: move to background thread
593                             storageManager.setVolumeNickname(fsUuid,
594                                     nickname.getText().toString());
595                         }
596                     });
597             builder.setNegativeButton(R.string.cancel, null);
598
599             return builder.create();
600         }
601     }
602
603     public static class OtherInfoFragment extends DialogFragment {
604         public static void show(Fragment parent, String title, VolumeInfo sharedVol) {
605             if (!parent.isAdded()) return;
606
607             final OtherInfoFragment dialog = new OtherInfoFragment();
608             dialog.setTargetFragment(parent, 0);
609             final Bundle args = new Bundle();
610             args.putString(Intent.EXTRA_TITLE, title);
611             args.putParcelable(Intent.EXTRA_INTENT, sharedVol.buildBrowseIntent());
612             dialog.setArguments(args);
613             dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO);
614         }
615
616         @Override
617         public Dialog onCreateDialog(Bundle savedInstanceState) {
618             final Context context = getActivity();
619
620             final String title = getArguments().getString(Intent.EXTRA_TITLE);
621             final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT);
622
623             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
624             builder.setMessage(
625                     TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title));
626
627             builder.setPositiveButton(R.string.storage_menu_explore,
628                     new DialogInterface.OnClickListener() {
629                         @Override
630                         public void onClick(DialogInterface dialog, int which) {
631                             startActivity(intent);
632                         }
633                     });
634             builder.setNegativeButton(android.R.string.cancel, null);
635
636             return builder.create();
637         }
638     }
639
640     public static class UserInfoFragment extends DialogFragment {
641         public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) {
642             if (!parent.isAdded()) return;
643
644             final UserInfoFragment dialog = new UserInfoFragment();
645             dialog.setTargetFragment(parent, 0);
646             final Bundle args = new Bundle();
647             args.putCharSequence(Intent.EXTRA_TITLE, userLabel);
648             args.putCharSequence(Intent.EXTRA_SUBJECT, userSize);
649             dialog.setArguments(args);
650             dialog.show(parent.getFragmentManager(), TAG_USER_INFO);
651         }
652
653         @Override
654         public Dialog onCreateDialog(Bundle savedInstanceState) {
655             final Context context = getActivity();
656
657             final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE);
658             final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT);
659
660             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
661             builder.setMessage(TextUtils.expandTemplate(
662                     getText(R.string.storage_detail_dialog_user), userLabel, userSize));
663
664             builder.setPositiveButton(android.R.string.ok, null);
665
666             return builder.create();
667         }
668     }
669
670     /**
671      * Dialog to request user confirmation before clearing all cache data.
672      */
673     public static class ConfirmClearCacheFragment extends DialogFragment {
674         public static void show(Fragment parent) {
675             if (!parent.isAdded()) return;
676
677             final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
678             dialog.setTargetFragment(parent, 0);
679             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
680         }
681
682         @Override
683         public Dialog onCreateDialog(Bundle savedInstanceState) {
684             final Context context = getActivity();
685
686             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
687             builder.setTitle(R.string.memory_clear_cache_title);
688             builder.setMessage(getString(R.string.memory_clear_cache_message));
689
690             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
691                 @Override
692                 public void onClick(DialogInterface dialog, int which) {
693                     final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment();
694                     final PackageManager pm = context.getPackageManager();
695                     final List<PackageInfo> infos = pm.getInstalledPackages(0);
696                     final ClearCacheObserver observer = new ClearCacheObserver(
697                             target, infos.size());
698                     for (PackageInfo info : infos) {
699                         pm.deleteApplicationCacheFiles(info.packageName, observer);
700                     }
701                 }
702             });
703             builder.setNegativeButton(android.R.string.cancel, null);
704
705             return builder.create();
706         }
707     }
708
709     private static class ClearCacheObserver extends IPackageDataObserver.Stub {
710         private final PrivateVolumeSettings mTarget;
711         private int mRemaining;
712
713         public ClearCacheObserver(PrivateVolumeSettings target, int remaining) {
714             mTarget = target;
715             mRemaining = remaining;
716         }
717
718         @Override
719         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
720             synchronized (this) {
721                 if (--mRemaining == 0) {
722                     mTarget.update();
723                 }
724             }
725         }
726     }
727 }