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