• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.Fragment;
22 import android.content.ActivityNotFoundException;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.IPackageDataObserver;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.UserInfo;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.os.storage.StorageEventListener;
36 import android.os.storage.StorageManager;
37 import android.os.storage.VolumeInfo;
38 import android.os.storage.VolumeRecord;
39 import android.provider.DocumentsContract;
40 import android.support.v7.preference.Preference;
41 import android.support.v7.preference.PreferenceCategory;
42 import android.support.v7.preference.PreferenceGroup;
43 import android.support.v7.preference.PreferenceScreen;
44 import android.text.TextUtils;
45 import android.text.format.Formatter;
46 import android.text.format.Formatter.BytesResult;
47 import android.util.Log;
48 import android.view.LayoutInflater;
49 import android.view.Menu;
50 import android.view.MenuInflater;
51 import android.view.MenuItem;
52 import android.view.View;
53 import android.widget.EditText;
54 
55 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
56 import com.android.settings.R;
57 import com.android.settings.Settings.StorageUseActivity;
58 import com.android.settings.SettingsPreferenceFragment;
59 import com.android.settings.Utils;
60 import com.android.settings.applications.ManageApplications;
61 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
62 import com.android.settings.deviceinfo.StorageSettings.MountTask;
63 import com.android.settingslib.deviceinfo.StorageMeasurement;
64 import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
65 import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementReceiver;
66 
67 import com.google.android.collect.Lists;
68 
69 import java.util.HashMap;
70 import java.util.List;
71 import java.util.Objects;
72 
73 /**
74  * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE}
75  * storage volume.
76  */
77 public class PrivateVolumeSettings extends SettingsPreferenceFragment {
78     // TODO: disable unmount when providing over MTP/PTP
79     // TODO: warn when mounted read-only
80 
81     private static final String TAG = "PrivateVolumeSettings";
82     private static final boolean LOGV = false;
83 
84     private static final String TAG_RENAME = "rename";
85     private static final String TAG_OTHER_INFO = "otherInfo";
86     private static final String TAG_SYSTEM_INFO = "systemInfo";
87     private static final String TAG_USER_INFO = "userInfo";
88     private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
89 
90     private static final String EXTRA_VOLUME_SIZE = "volume_size";
91 
92     private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
93 
94     private static final int[] ITEMS_NO_SHOW_SHARED = new int[] {
95             R.string.storage_detail_apps,
96             R.string.storage_detail_system,
97     };
98 
99     private static final int[] ITEMS_SHOW_SHARED = new int[] {
100             R.string.storage_detail_apps,
101             R.string.storage_detail_images,
102             R.string.storage_detail_videos,
103             R.string.storage_detail_audio,
104             R.string.storage_detail_system,
105             R.string.storage_detail_other,
106     };
107 
108     private StorageManager mStorageManager;
109     private UserManager mUserManager;
110 
111     private String mVolumeId;
112     private VolumeInfo mVolume;
113     private VolumeInfo mSharedVolume;
114     private long mTotalSize;
115     private long mSystemSize;
116 
117     private StorageMeasurement mMeasure;
118 
119     private UserInfo mCurrentUser;
120 
121     private StorageSummaryPreference mSummary;
122     private List<StorageItemPreference> mItemPreferencePool = Lists.newArrayList();
123     private List<PreferenceCategory> mHeaderPreferencePool = Lists.newArrayList();
124     private int mHeaderPoolIndex;
125     private int mItemPoolIndex;
126 
127     private Preference mExplore;
128 
129     private boolean mNeedsUpdate;
130 
isVolumeValid()131     private boolean isVolumeValid() {
132         return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
133                 && mVolume.isMountedReadable();
134     }
135 
PrivateVolumeSettings()136     public PrivateVolumeSettings() {
137         setRetainInstance(true);
138     }
139 
140     @Override
getMetricsCategory()141     public int getMetricsCategory() {
142         return MetricsEvent.DEVICEINFO_STORAGE;
143     }
144 
145     @Override
onCreate(Bundle icicle)146     public void onCreate(Bundle icicle) {
147         super.onCreate(icicle);
148 
149         final Context context = getActivity();
150 
151         mUserManager = context.getSystemService(UserManager.class);
152         mStorageManager = context.getSystemService(StorageManager.class);
153 
154         mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
155         mVolume = mStorageManager.findVolumeById(mVolumeId);
156 
157         final long sharedDataSize = mVolume.getPath().getTotalSpace();
158         mTotalSize = getArguments().getLong(EXTRA_VOLUME_SIZE, 0);
159         mSystemSize = mTotalSize - sharedDataSize;
160         if (LOGV) Log.v(TAG,
161                 "onCreate() mTotalSize: " + mTotalSize + " sharedDataSize: " + sharedDataSize);
162 
163         if (mTotalSize <= 0) {
164             mTotalSize = sharedDataSize;
165             mSystemSize = 0;
166         }
167 
168         // Find the emulated shared storage layered above this private volume
169         mSharedVolume = mStorageManager.findEmulatedForPrivate(mVolume);
170 
171         mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume);
172         mMeasure.setReceiver(mReceiver);
173 
174         if (!isVolumeValid()) {
175             getActivity().finish();
176             return;
177         }
178 
179         addPreferencesFromResource(R.xml.device_info_storage_volume);
180         getPreferenceScreen().setOrderingAsAdded(true);
181 
182         mSummary = new StorageSummaryPreference(getPrefContext());
183         mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId());
184 
185         mExplore = buildAction(R.string.storage_menu_explore);
186 
187         mNeedsUpdate = true;
188 
189         setHasOptionsMenu(true);
190     }
191 
setTitle()192     private void setTitle() {
193         getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume));
194     }
195 
update()196     private void update() {
197         if (!isVolumeValid()) {
198             getActivity().finish();
199             return;
200         }
201 
202         setTitle();
203 
204         // Valid options may have changed
205         getFragmentManager().invalidateOptionsMenu();
206 
207         final Context context = getActivity();
208         final PreferenceScreen screen = getPreferenceScreen();
209 
210         screen.removeAll();
211 
212         addPreference(screen, mSummary);
213 
214         List<UserInfo> allUsers = mUserManager.getUsers();
215         final int userCount = allUsers.size();
216         final boolean showHeaders = userCount > 1;
217         final boolean showShared = (mSharedVolume != null) && mSharedVolume.isMountedReadable();
218 
219         mItemPoolIndex = 0;
220         mHeaderPoolIndex = 0;
221 
222         int addedUserCount = 0;
223         // Add current user and its profiles first
224         for (int userIndex = 0; userIndex < userCount; ++userIndex) {
225             final UserInfo userInfo = allUsers.get(userIndex);
226             if (Utils.isProfileOf(mCurrentUser, userInfo)) {
227                 final PreferenceGroup details = showHeaders ?
228                         addCategory(screen, userInfo.name) : screen;
229                 addDetailItems(details, showShared, userInfo.id);
230                 ++addedUserCount;
231             }
232         }
233 
234         // Add rest of users
235         if (userCount - addedUserCount > 0) {
236             PreferenceGroup otherUsers = addCategory(screen,
237                     getText(R.string.storage_other_users));
238             for (int userIndex = 0; userIndex < userCount; ++userIndex) {
239                 final UserInfo userInfo = allUsers.get(userIndex);
240                 if (!Utils.isProfileOf(mCurrentUser, userInfo)) {
241                     addItem(otherUsers, /* titleRes */ 0, userInfo.name, userInfo.id);
242                 }
243             }
244         }
245 
246         addItem(screen, R.string.storage_detail_cached, null, UserHandle.USER_NULL);
247 
248         if (showShared) {
249             addPreference(screen, mExplore);
250         }
251 
252         final long freeBytes = mVolume.getPath().getFreeSpace();
253         final long usedBytes = mTotalSize - freeBytes;
254 
255         if (LOGV) Log.v(TAG, "update() freeBytes: " + freeBytes + " usedBytes: " + usedBytes);
256 
257         final BytesResult result = Formatter.formatBytes(getResources(), usedBytes, 0);
258         mSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
259                 result.value, result.units));
260         mSummary.setSummary(getString(R.string.storage_volume_used,
261                 Formatter.formatFileSize(context, mTotalSize)));
262         mSummary.setPercent(usedBytes, mTotalSize);
263 
264         mMeasure.forceMeasure();
265         mNeedsUpdate = false;
266     }
267 
addPreference(PreferenceGroup group, Preference pref)268     private void addPreference(PreferenceGroup group, Preference pref) {
269         pref.setOrder(Preference.DEFAULT_ORDER);
270         group.addPreference(pref);
271     }
272 
addCategory(PreferenceGroup group, CharSequence title)273     private PreferenceCategory addCategory(PreferenceGroup group, CharSequence title) {
274         PreferenceCategory category;
275         if (mHeaderPoolIndex < mHeaderPreferencePool.size()) {
276             category = mHeaderPreferencePool.get(mHeaderPoolIndex);
277         } else {
278             category = new PreferenceCategory(getPrefContext());
279             mHeaderPreferencePool.add(category);
280         }
281         category.setTitle(title);
282         category.removeAll();
283         addPreference(group, category);
284         ++mHeaderPoolIndex;
285         return category;
286     }
287 
addDetailItems(PreferenceGroup category, boolean showShared, int userId)288     private void addDetailItems(PreferenceGroup category, boolean showShared, int userId) {
289         final int[] itemsToAdd = (showShared ? ITEMS_SHOW_SHARED : ITEMS_NO_SHOW_SHARED);
290         for (int i = 0; i < itemsToAdd.length; ++i) {
291             addItem(category, itemsToAdd[i], null, userId);
292         }
293     }
294 
addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId)295     private void addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId) {
296         if (titleRes == R.string.storage_detail_system) {
297             if (mSystemSize <= 0) {
298                 Log.w(TAG, "Skipping System storage because its size is " + mSystemSize);
299                 return;
300             }
301             if (userId != UserHandle.myUserId()) {
302                 // Only display system on current user.
303                 return;
304             }
305         }
306         StorageItemPreference item;
307         if (mItemPoolIndex < mItemPreferencePool.size()) {
308             item = mItemPreferencePool.get(mItemPoolIndex);
309         } else {
310             item = buildItem();
311             mItemPreferencePool.add(item);
312         }
313         if (title != null) {
314             item.setTitle(title);
315             item.setKey(title.toString());
316         } else {
317             item.setTitle(titleRes);
318             item.setKey(Integer.toString(titleRes));
319         }
320         item.setSummary(R.string.memory_calculating_size);
321         item.userHandle = userId;
322         addPreference(group, item);
323         ++mItemPoolIndex;
324     }
325 
buildItem()326     private StorageItemPreference buildItem() {
327         final StorageItemPreference item = new StorageItemPreference(getPrefContext());
328         item.setIcon(R.drawable.empty_icon);
329         return item;
330     }
331 
buildAction(int titleRes)332     private Preference buildAction(int titleRes) {
333         final Preference pref = new Preference(getPrefContext());
334         pref.setTitle(titleRes);
335         pref.setKey(Integer.toString(titleRes));
336         return pref;
337     }
338 
setVolumeSize(Bundle args, long size)339     static void setVolumeSize(Bundle args, long size) {
340         args.putLong(EXTRA_VOLUME_SIZE, size);
341     }
342 
343     @Override
onResume()344     public void onResume() {
345         super.onResume();
346 
347         // Refresh to verify that we haven't been formatted away
348         mVolume = mStorageManager.findVolumeById(mVolumeId);
349         if (!isVolumeValid()) {
350             getActivity().finish();
351             return;
352         }
353 
354         mStorageManager.registerListener(mStorageListener);
355 
356         if (mNeedsUpdate) {
357             update();
358         } else {
359             setTitle();
360         }
361     }
362 
363     @Override
onPause()364     public void onPause() {
365         super.onPause();
366         mStorageManager.unregisterListener(mStorageListener);
367     }
368 
369     @Override
onDestroy()370     public void onDestroy() {
371         super.onDestroy();
372         if (mMeasure != null) {
373             mMeasure.onDestroy();
374         }
375     }
376 
377     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)378     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
379         super.onCreateOptionsMenu(menu, inflater);
380         inflater.inflate(R.menu.storage_volume, menu);
381     }
382 
383     @Override
onPrepareOptionsMenu(Menu menu)384     public void onPrepareOptionsMenu(Menu menu) {
385         if (!isVolumeValid()) return;
386 
387         final MenuItem rename = menu.findItem(R.id.storage_rename);
388         final MenuItem mount = menu.findItem(R.id.storage_mount);
389         final MenuItem unmount = menu.findItem(R.id.storage_unmount);
390         final MenuItem format = menu.findItem(R.id.storage_format);
391         final MenuItem migrate = menu.findItem(R.id.storage_migrate);
392         final MenuItem manage = menu.findItem(R.id.storage_free);
393 
394         // Actions live in menu for non-internal private volumes; they're shown
395         // as preference items for public volumes.
396         if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.getId())) {
397             rename.setVisible(false);
398             mount.setVisible(false);
399             unmount.setVisible(false);
400             format.setVisible(false);
401             manage.setVisible(getResources().getBoolean(
402                     R.bool.config_storage_manager_settings_enabled));
403         } else {
404             rename.setVisible(mVolume.getType() == VolumeInfo.TYPE_PRIVATE);
405             mount.setVisible(mVolume.getState() == VolumeInfo.STATE_UNMOUNTED);
406             unmount.setVisible(mVolume.isMountedReadable());
407             format.setVisible(true);
408             manage.setVisible(false);
409         }
410 
411         format.setTitle(R.string.storage_menu_format_public);
412 
413         // Only offer to migrate when not current storage
414         final VolumeInfo privateVol = getActivity().getPackageManager()
415                 .getPrimaryStorageCurrentVolume();
416         migrate.setVisible((privateVol != null)
417                 && (privateVol.getType() == VolumeInfo.TYPE_PRIVATE)
418                 && !Objects.equals(mVolume, privateVol));
419     }
420 
421     @Override
onOptionsItemSelected(MenuItem item)422     public boolean onOptionsItemSelected(MenuItem item) {
423         final Context context = getActivity();
424         final Bundle args = new Bundle();
425         switch (item.getItemId()) {
426             case R.id.storage_rename:
427                 RenameFragment.show(this, mVolume);
428                 return true;
429             case R.id.storage_mount:
430                 new MountTask(context, mVolume).execute();
431                 return true;
432             case R.id.storage_unmount:
433                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
434                 startFragment(this, PrivateVolumeUnmount.class.getCanonicalName(),
435                         R.string.storage_menu_unmount, 0, args);
436                 return true;
437             case R.id.storage_format:
438                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
439                 startFragment(this, PrivateVolumeFormat.class.getCanonicalName(),
440                         R.string.storage_menu_format, 0, args);
441                 return true;
442             case R.id.storage_migrate:
443                 final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class);
444                 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
445                 startActivity(intent);
446                 return true;
447             case R.id.storage_free:
448                 final Intent deletion_helper_intent =
449                         new Intent(StorageManager.ACTION_MANAGE_STORAGE);
450                 startActivity(deletion_helper_intent);
451                 return true;
452         }
453         return super.onOptionsItemSelected(item);
454     }
455 
456     @Override
onPreferenceTreeClick(Preference pref)457     public boolean onPreferenceTreeClick(Preference pref) {
458         // TODO: launch better intents for specific volume
459 
460         final int userId = (pref instanceof StorageItemPreference ?
461                 ((StorageItemPreference) pref).userHandle : -1);
462         int itemTitleId;
463         try {
464             itemTitleId = Integer.parseInt(pref.getKey());
465         } catch (NumberFormatException e) {
466             itemTitleId = 0;
467         }
468         Intent intent = null;
469         switch (itemTitleId) {
470             case R.string.storage_detail_apps: {
471                 Bundle args = new Bundle();
472                 args.putString(ManageApplications.EXTRA_CLASSNAME,
473                         StorageUseActivity.class.getName());
474                 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
475                 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
476                 intent = Utils.onBuildStartFragmentIntent(getActivity(),
477                         ManageApplications.class.getName(), args, null, R.string.apps_storage, null,
478                         false, getMetricsCategory());
479 
480             } break;
481             case R.string.storage_detail_images: {
482                 intent = getIntentForStorage(AUTHORITY_MEDIA, "images_root");
483             } break;
484             case R.string.storage_detail_videos: {
485                 intent = getIntentForStorage(AUTHORITY_MEDIA, "videos_root");
486             } break;
487             case R.string.storage_detail_audio: {
488                 intent = getIntentForStorage(AUTHORITY_MEDIA, "audio_root");
489             } break;
490             case R.string.storage_detail_system: {
491                 SystemInfoFragment.show(this);
492                 return true;
493 
494             }
495             case R.string.storage_detail_other: {
496                 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
497                         mSharedVolume, userId);
498                 return true;
499 
500             }
501             case R.string.storage_detail_cached: {
502                 ConfirmClearCacheFragment.show(this);
503                 return true;
504 
505             }
506             case R.string.storage_menu_explore: {
507                 intent = mSharedVolume.buildBrowseIntent();
508             } break;
509             case 0: {
510                 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
511                 return true;
512             }
513         }
514 
515         if (intent != null) {
516             intent.putExtra(Intent.EXTRA_USER_ID, userId);
517 
518             Utils.launchIntent(this, intent);
519             return true;
520         }
521         return super.onPreferenceTreeClick(pref);
522     }
523 
getIntentForStorage(String authority, String root)524     private Intent getIntentForStorage(String authority, String root) {
525         Intent intent = new Intent(Intent.ACTION_VIEW);
526         intent.setDataAndType(
527                 DocumentsContract.buildRootUri(authority, root),
528                 DocumentsContract.Root.MIME_TYPE_ITEM);
529         intent.addCategory(Intent.CATEGORY_DEFAULT);
530 
531         return intent;
532     }
533 
534     private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
535         @Override
536         public void onDetailsChanged(MeasurementDetails details) {
537             updateDetails(details);
538         }
539     };
540 
updateDetails(MeasurementDetails details)541     private void updateDetails(MeasurementDetails details) {
542         StorageItemPreference otherItem = null;
543         long accountedSize = 0;
544         long totalMiscSize = 0;
545         long totalDownloadsSize = 0;
546 
547         for (int i = 0; i < mItemPoolIndex; ++i) {
548             StorageItemPreference item = mItemPreferencePool.get(i);
549             final int userId = item.userHandle;
550             int itemTitleId;
551             try {
552                 itemTitleId = Integer.parseInt(item.getKey());
553             } catch (NumberFormatException e) {
554                 itemTitleId = 0;
555             }
556             switch (itemTitleId) {
557                 case R.string.storage_detail_system: {
558                     updatePreference(item, mSystemSize);
559                     accountedSize += mSystemSize;
560                     if (LOGV) Log.v(TAG, "mSystemSize: " + mSystemSize
561                             + " accountedSize: " + accountedSize);
562                 } break;
563                 case R.string.storage_detail_apps: {
564                     updatePreference(item, details.appsSize.get(userId));
565                     accountedSize += details.appsSize.get(userId);
566                     if (LOGV) Log.v(TAG, "appsSize: " + details.appsSize.get(userId)
567                             + " accountedSize: " + accountedSize);
568                 } break;
569                 case R.string.storage_detail_images: {
570                     final long imagesSize = totalValues(details, userId,
571                             Environment.DIRECTORY_DCIM, Environment.DIRECTORY_PICTURES);
572                     updatePreference(item, imagesSize);
573                     accountedSize += imagesSize;
574                     if (LOGV) Log.v(TAG, "imagesSize: " + imagesSize
575                             + " accountedSize: " + accountedSize);
576                 } break;
577                 case R.string.storage_detail_videos: {
578                     final long videosSize = totalValues(details, userId,
579                             Environment.DIRECTORY_MOVIES);
580                     updatePreference(item, videosSize);
581                     accountedSize += videosSize;
582                     if (LOGV) Log.v(TAG, "videosSize: " + videosSize
583                             + " accountedSize: " + accountedSize);
584                 } break;
585                 case R.string.storage_detail_audio: {
586                     final long audioSize = totalValues(details, userId,
587                             Environment.DIRECTORY_MUSIC,
588                             Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
589                             Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
590                     updatePreference(item, audioSize);
591                     accountedSize += audioSize;
592                     if (LOGV) Log.v(TAG, "audioSize: " + audioSize
593                             + " accountedSize: " + accountedSize);
594                 } break;
595                 case R.string.storage_detail_other: {
596                     final long downloadsSize = totalValues(details, userId,
597                             Environment.DIRECTORY_DOWNLOADS);
598                     final long miscSize = details.miscSize.get(userId);
599                     totalDownloadsSize += downloadsSize;
600                     totalMiscSize += miscSize;
601                     accountedSize += miscSize + downloadsSize;
602 
603                     if (LOGV)
604                         Log.v(TAG, "miscSize for " + userId + ": " + miscSize + "(total: "
605                                 + totalMiscSize + ") \ndownloadsSize: " + downloadsSize + "(total: "
606                                 + totalDownloadsSize + ") accountedSize: " + accountedSize);
607 
608                     // Cannot display 'Other' until all known items are accounted for.
609                     otherItem = item;
610                 } break;
611                 case R.string.storage_detail_cached: {
612                     updatePreference(item, details.cacheSize);
613                     accountedSize += details.cacheSize;
614                     if (LOGV)
615                         Log.v(TAG, "cacheSize: " + details.cacheSize + " accountedSize: "
616                                 + accountedSize);
617                 } break;
618                 case 0: {
619                     final long userSize = details.usersSize.get(userId);
620                     updatePreference(item, userSize);
621                     accountedSize += userSize;
622                     if (LOGV) Log.v(TAG, "userSize: " + userSize
623                             + " accountedSize: " + accountedSize);
624                 } break;
625             }
626         }
627         if (otherItem != null) {
628             final long usedSize = mTotalSize - details.availSize;
629             final long unaccountedSize = usedSize - accountedSize;
630             final long otherSize = totalMiscSize + totalDownloadsSize + unaccountedSize;
631             Log.v(TAG, "Other items: \n\tmTotalSize: " + mTotalSize + " availSize: "
632                     + details.availSize + " usedSize: " + usedSize + "\n\taccountedSize: "
633                     + accountedSize + " unaccountedSize size: " + unaccountedSize
634                     + "\n\ttotalMiscSize: " + totalMiscSize + " totalDownloadsSize: "
635                     + totalDownloadsSize + "\n\tdetails: " + details);
636             updatePreference(otherItem, otherSize);
637         }
638     }
639 
updatePreference(StorageItemPreference pref, long size)640     private void updatePreference(StorageItemPreference pref, long size) {
641         pref.setStorageSize(size, mTotalSize);
642     }
643 
totalValues(MeasurementDetails details, int userId, String... keys)644     private static long totalValues(MeasurementDetails details, int userId, String... keys) {
645         long total = 0;
646         HashMap<String, Long> map = details.mediaSize.get(userId);
647         if (map != null) {
648             for (String key : keys) {
649                 if (map.containsKey(key)) {
650                     total += map.get(key);
651                 }
652             }
653         } else {
654             Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
655         }
656         return total;
657     }
658 
659     private final StorageEventListener mStorageListener = new StorageEventListener() {
660         @Override
661         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
662             if (Objects.equals(mVolume.getId(), vol.getId())) {
663                 mVolume = vol;
664                 update();
665             }
666         }
667 
668         @Override
669         public void onVolumeRecordChanged(VolumeRecord rec) {
670             if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
671                 mVolume = mStorageManager.findVolumeById(mVolumeId);
672                 update();
673             }
674         }
675     };
676 
677     /**
678      * Dialog that allows editing of volume nickname.
679      */
680     public static class RenameFragment extends InstrumentedDialogFragment {
show(PrivateVolumeSettings parent, VolumeInfo vol)681         public static void show(PrivateVolumeSettings parent, VolumeInfo vol) {
682             if (!parent.isAdded()) return;
683 
684             final RenameFragment dialog = new RenameFragment();
685             dialog.setTargetFragment(parent, 0);
686             final Bundle args = new Bundle();
687             args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
688             dialog.setArguments(args);
689             dialog.show(parent.getFragmentManager(), TAG_RENAME);
690         }
691 
692         @Override
getMetricsCategory()693         public int getMetricsCategory() {
694             return MetricsEvent.DIALOG_VOLUME_RENAME;
695         }
696 
697         @Override
onCreateDialog(Bundle savedInstanceState)698         public Dialog onCreateDialog(Bundle savedInstanceState) {
699             final Context context = getActivity();
700             final StorageManager storageManager = context.getSystemService(StorageManager.class);
701 
702             final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
703             final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid);
704             final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid);
705 
706             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
707             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
708 
709             final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false);
710             final EditText nickname = (EditText) view.findViewById(R.id.edittext);
711             nickname.setText(rec.getNickname());
712 
713             builder.setTitle(R.string.storage_rename_title);
714             builder.setView(view);
715 
716             builder.setPositiveButton(R.string.save,
717                     new DialogInterface.OnClickListener() {
718                         @Override
719                         public void onClick(DialogInterface dialog, int which) {
720                             // TODO: move to background thread
721                             storageManager.setVolumeNickname(fsUuid,
722                                     nickname.getText().toString());
723                         }
724                     });
725             builder.setNegativeButton(R.string.cancel, null);
726 
727             return builder.create();
728         }
729     }
730 
731     public static class SystemInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent)732         public static void show(Fragment parent) {
733             if (!parent.isAdded()) return;
734 
735             final SystemInfoFragment dialog = new SystemInfoFragment();
736             dialog.setTargetFragment(parent, 0);
737             dialog.show(parent.getFragmentManager(), TAG_SYSTEM_INFO);
738         }
739 
740         @Override
getMetricsCategory()741         public int getMetricsCategory() {
742             return MetricsEvent.DIALOG_STORAGE_SYSTEM_INFO;
743         }
744 
745         @Override
onCreateDialog(Bundle savedInstanceState)746         public Dialog onCreateDialog(Bundle savedInstanceState) {
747             return new AlertDialog.Builder(getActivity())
748                     .setMessage(getContext().getString(R.string.storage_detail_dialog_system,
749                             Build.VERSION.RELEASE))
750                     .setPositiveButton(android.R.string.ok, null)
751                     .create();
752         }
753     }
754 
755     public static class OtherInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent, String title, VolumeInfo sharedVol, int userId)756         public static void show(Fragment parent, String title, VolumeInfo sharedVol, int userId) {
757             if (!parent.isAdded()) return;
758 
759             final OtherInfoFragment dialog = new OtherInfoFragment();
760             dialog.setTargetFragment(parent, 0);
761             final Bundle args = new Bundle();
762             args.putString(Intent.EXTRA_TITLE, title);
763 
764             final Intent intent = sharedVol.buildBrowseIntent();
765             intent.putExtra(Intent.EXTRA_USER_ID, userId);
766             args.putParcelable(Intent.EXTRA_INTENT, intent);
767             dialog.setArguments(args);
768             dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO);
769         }
770 
771         @Override
getMetricsCategory()772         public int getMetricsCategory() {
773             return MetricsEvent.DIALOG_STORAGE_OTHER_INFO;
774         }
775 
776         @Override
onCreateDialog(Bundle savedInstanceState)777         public Dialog onCreateDialog(Bundle savedInstanceState) {
778             final Context context = getActivity();
779 
780             final String title = getArguments().getString(Intent.EXTRA_TITLE);
781             final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT);
782 
783             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
784             builder.setMessage(
785                     TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title));
786 
787             builder.setPositiveButton(R.string.storage_menu_explore,
788                     new DialogInterface.OnClickListener() {
789                         @Override
790                         public void onClick(DialogInterface dialog, int which) {
791                             Utils.launchIntent(OtherInfoFragment.this, intent);
792                         }
793                     });
794             builder.setNegativeButton(android.R.string.cancel, null);
795 
796             return builder.create();
797         }
798     }
799 
800     public static class UserInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent, CharSequence userLabel, CharSequence userSize)801         public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) {
802             if (!parent.isAdded()) return;
803 
804             final UserInfoFragment dialog = new UserInfoFragment();
805             dialog.setTargetFragment(parent, 0);
806             final Bundle args = new Bundle();
807             args.putCharSequence(Intent.EXTRA_TITLE, userLabel);
808             args.putCharSequence(Intent.EXTRA_SUBJECT, userSize);
809             dialog.setArguments(args);
810             dialog.show(parent.getFragmentManager(), TAG_USER_INFO);
811         }
812 
813         @Override
getMetricsCategory()814         public int getMetricsCategory() {
815             return MetricsEvent.DIALOG_STORAGE_USER_INFO;
816         }
817 
818         @Override
onCreateDialog(Bundle savedInstanceState)819         public Dialog onCreateDialog(Bundle savedInstanceState) {
820             final Context context = getActivity();
821 
822             final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE);
823             final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT);
824 
825             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
826             builder.setMessage(TextUtils.expandTemplate(
827                     getText(R.string.storage_detail_dialog_user), userLabel, userSize));
828 
829             builder.setPositiveButton(android.R.string.ok, null);
830 
831             return builder.create();
832         }
833     }
834 
835     /**
836      * Dialog to request user confirmation before clearing all cache data.
837      */
838     public static class ConfirmClearCacheFragment extends InstrumentedDialogFragment {
show(Fragment parent)839         public static void show(Fragment parent) {
840             if (!parent.isAdded()) return;
841 
842             final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
843             dialog.setTargetFragment(parent, 0);
844             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
845         }
846 
847         @Override
getMetricsCategory()848         public int getMetricsCategory() {
849             return MetricsEvent.DIALOG_STORAGE_CLEAR_CACHE;
850         }
851 
852         @Override
onCreateDialog(Bundle savedInstanceState)853         public Dialog onCreateDialog(Bundle savedInstanceState) {
854             final Context context = getActivity();
855 
856             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
857             builder.setTitle(R.string.memory_clear_cache_title);
858             builder.setMessage(getString(R.string.memory_clear_cache_message));
859 
860             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
861                 @Override
862                 public void onClick(DialogInterface dialog, int which) {
863                     final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment();
864                     final PackageManager pm = context.getPackageManager();
865                     final UserManager um = context.getSystemService(UserManager.class);
866 
867                     for (int userId : um.getProfileIdsWithDisabled(context.getUserId())) {
868                         final List<PackageInfo> infos = pm.getInstalledPackagesAsUser(0, userId);
869                         final ClearCacheObserver observer = new ClearCacheObserver(
870                                 target, infos.size());
871                         for (PackageInfo info : infos) {
872                             pm.deleteApplicationCacheFilesAsUser(info.packageName, userId,
873                                     observer);
874                         }
875                     }
876                 }
877             });
878             builder.setNegativeButton(android.R.string.cancel, null);
879 
880             return builder.create();
881         }
882     }
883 
884     private static class ClearCacheObserver extends IPackageDataObserver.Stub {
885         private final PrivateVolumeSettings mTarget;
886         private int mRemaining;
887 
ClearCacheObserver(PrivateVolumeSettings target, int remaining)888         public ClearCacheObserver(PrivateVolumeSettings target, int remaining) {
889             mTarget = target;
890             mRemaining = remaining;
891         }
892 
893         @Override
onRemoveCompleted(final String packageName, final boolean succeeded)894         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
895             synchronized (this) {
896                 if (--mRemaining == 0) {
897                     mTarget.getActivity().runOnUiThread(new Runnable() {
898                         @Override
899                         public void run() {
900                             mTarget.update();
901                         }
902                     });
903                 }
904             }
905         }
906     }
907 }
908