PrivateVolumeSettings.java revision 4bbe3d1fb66a99bfdf4cad8f2e77df23dccebbbd
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
17package com.android.settings.deviceinfo;
18
19import static com.android.settings.deviceinfo.StorageSettings.TAG;
20
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
25import android.content.ActivityNotFoundException;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.pm.IPackageDataObserver;
30import android.content.pm.PackageInfo;
31import android.content.pm.PackageManager;
32import android.content.pm.UserInfo;
33import android.os.Bundle;
34import android.os.Environment;
35import android.os.UserHandle;
36import android.os.UserManager;
37import android.os.storage.StorageEventListener;
38import android.os.storage.StorageManager;
39import android.os.storage.VolumeInfo;
40import android.os.storage.VolumeRecord;
41import android.preference.Preference;
42import android.preference.PreferenceCategory;
43import android.preference.PreferenceGroup;
44import android.preference.PreferenceScreen;
45import android.provider.DocumentsContract;
46import android.text.TextUtils;
47import android.text.format.Formatter;
48import android.text.format.Formatter.BytesResult;
49import android.util.Log;
50import android.view.LayoutInflater;
51import android.view.Menu;
52import android.view.MenuInflater;
53import android.view.MenuItem;
54import android.view.View;
55import android.widget.EditText;
56
57import com.android.internal.logging.MetricsLogger;
58import com.android.settings.R;
59import com.android.settings.Settings.StorageUseActivity;
60import com.android.settings.SettingsPreferenceFragment;
61import com.android.settings.Utils;
62import com.android.settings.applications.ManageApplications;
63import com.android.settings.deviceinfo.StorageSettings.MountTask;
64import com.android.settingslib.deviceinfo.StorageMeasurement;
65import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
66import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementReceiver;
67import com.google.android.collect.Lists;
68
69import java.io.File;
70import java.util.HashMap;
71import java.util.List;
72import java.util.Objects;
73
74/**
75 * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE}
76 * storage volume.
77 */
78public 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            throw new IllegalStateException(
531                    "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}
728