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                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