• 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.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.IPackageDataObserver;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.UserInfo;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Environment;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.os.storage.StorageEventListener;
35 import android.os.storage.StorageManager;
36 import android.os.storage.VolumeInfo;
37 import android.os.storage.VolumeRecord;
38 import android.provider.DocumentsContract;
39 import android.support.v7.preference.Preference;
40 import android.support.v7.preference.PreferenceCategory;
41 import android.support.v7.preference.PreferenceGroup;
42 import android.support.v7.preference.PreferenceScreen;
43 import android.text.TextUtils;
44 import android.text.format.Formatter;
45 import android.text.format.Formatter.BytesResult;
46 import android.util.Log;
47 import android.view.LayoutInflater;
48 import android.view.Menu;
49 import android.view.MenuInflater;
50 import android.view.MenuItem;
51 import android.view.View;
52 import android.widget.EditText;
53 
54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
55 import com.android.settings.R;
56 import com.android.settings.Settings.StorageUseActivity;
57 import com.android.settings.SettingsPreferenceFragment;
58 import com.android.settings.Utils;
59 import com.android.settings.applications.manageapplications.ManageApplications;
60 import com.android.settings.core.SubSettingLauncher;
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                 new SubSettingLauncher(context)
435                         .setDestination(PrivateVolumeUnmount.class.getCanonicalName())
436                         .setTitle(R.string.storage_menu_unmount)
437                         .setSourceMetricsCategory(getMetricsCategory())
438                         .setArguments(args)
439                         .launch();
440                 return true;
441             case R.id.storage_format:
442                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
443                 new SubSettingLauncher(context)
444                         .setDestination(PrivateVolumeFormat.class.getCanonicalName())
445                         .setTitle(R.string.storage_menu_format)
446                         .setSourceMetricsCategory(getMetricsCategory())
447                         .setArguments(args)
448                         .launch();
449                 return true;
450             case R.id.storage_migrate:
451                 final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class);
452                 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
453                 startActivity(intent);
454                 return true;
455             case R.id.storage_free:
456                 final Intent deletion_helper_intent =
457                         new Intent(StorageManager.ACTION_MANAGE_STORAGE);
458                 startActivity(deletion_helper_intent);
459                 return true;
460         }
461         return super.onOptionsItemSelected(item);
462     }
463 
464     @Override
onPreferenceTreeClick(Preference pref)465     public boolean onPreferenceTreeClick(Preference pref) {
466         // TODO: launch better intents for specific volume
467 
468         final int userId = (pref instanceof StorageItemPreference ?
469                 ((StorageItemPreference) pref).userHandle : -1);
470         int itemTitleId;
471         try {
472             itemTitleId = Integer.parseInt(pref.getKey());
473         } catch (NumberFormatException e) {
474             itemTitleId = 0;
475         }
476         Intent intent = null;
477         switch (itemTitleId) {
478             case R.string.storage_detail_apps: {
479                 Bundle args = new Bundle();
480                 args.putString(ManageApplications.EXTRA_CLASSNAME,
481                         StorageUseActivity.class.getName());
482                 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
483                 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
484                 args.putInt(
485                         ManageApplications.EXTRA_STORAGE_TYPE,
486                         ManageApplications.STORAGE_TYPE_LEGACY);
487                 intent = new SubSettingLauncher(getActivity())
488                         .setDestination(ManageApplications.class.getName())
489                         .setArguments(args)
490                         .setTitle(R.string.apps_storage)
491                         .setSourceMetricsCategory(getMetricsCategory())
492                         .toIntent();
493 
494             } break;
495             case R.string.storage_detail_images: {
496                 intent = getIntentForStorage(AUTHORITY_MEDIA, "images_root");
497             } break;
498             case R.string.storage_detail_videos: {
499                 intent = getIntentForStorage(AUTHORITY_MEDIA, "videos_root");
500             } break;
501             case R.string.storage_detail_audio: {
502                 intent = getIntentForStorage(AUTHORITY_MEDIA, "audio_root");
503             } break;
504             case R.string.storage_detail_system: {
505                 SystemInfoFragment.show(this);
506                 return true;
507 
508             }
509             case R.string.storage_detail_other: {
510                 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
511                         mSharedVolume, userId);
512                 return true;
513 
514             }
515             case R.string.storage_detail_cached: {
516                 ConfirmClearCacheFragment.show(this);
517                 return true;
518 
519             }
520             case R.string.storage_menu_explore: {
521                 intent = mSharedVolume.buildBrowseIntent();
522             } break;
523             case 0: {
524                 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
525                 return true;
526             }
527         }
528 
529         if (intent != null) {
530             intent.putExtra(Intent.EXTRA_USER_ID, userId);
531 
532             Utils.launchIntent(this, intent);
533             return true;
534         }
535         return super.onPreferenceTreeClick(pref);
536     }
537 
getIntentForStorage(String authority, String root)538     private Intent getIntentForStorage(String authority, String root) {
539         Intent intent = new Intent(Intent.ACTION_VIEW);
540         intent.setDataAndType(
541                 DocumentsContract.buildRootUri(authority, root),
542                 DocumentsContract.Root.MIME_TYPE_ITEM);
543         intent.addCategory(Intent.CATEGORY_DEFAULT);
544 
545         return intent;
546     }
547 
548     private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
549         @Override
550         public void onDetailsChanged(MeasurementDetails details) {
551             updateDetails(details);
552         }
553     };
554 
updateDetails(MeasurementDetails details)555     private void updateDetails(MeasurementDetails details) {
556         StorageItemPreference otherItem = null;
557         long accountedSize = 0;
558         long totalMiscSize = 0;
559         long totalDownloadsSize = 0;
560 
561         for (int i = 0; i < mItemPoolIndex; ++i) {
562             StorageItemPreference item = mItemPreferencePool.get(i);
563             final int userId = item.userHandle;
564             int itemTitleId;
565             try {
566                 itemTitleId = Integer.parseInt(item.getKey());
567             } catch (NumberFormatException e) {
568                 itemTitleId = 0;
569             }
570             switch (itemTitleId) {
571                 case R.string.storage_detail_system: {
572                     updatePreference(item, mSystemSize);
573                     accountedSize += mSystemSize;
574                     if (LOGV) Log.v(TAG, "mSystemSize: " + mSystemSize
575                             + " accountedSize: " + accountedSize);
576                 } break;
577                 case R.string.storage_detail_apps: {
578                     updatePreference(item, details.appsSize.get(userId));
579                     accountedSize += details.appsSize.get(userId);
580                     if (LOGV) Log.v(TAG, "appsSize: " + details.appsSize.get(userId)
581                             + " accountedSize: " + accountedSize);
582                 } break;
583                 case R.string.storage_detail_images: {
584                     final long imagesSize = totalValues(details, userId,
585                             Environment.DIRECTORY_DCIM, Environment.DIRECTORY_PICTURES);
586                     updatePreference(item, imagesSize);
587                     accountedSize += imagesSize;
588                     if (LOGV) Log.v(TAG, "imagesSize: " + imagesSize
589                             + " accountedSize: " + accountedSize);
590                 } break;
591                 case R.string.storage_detail_videos: {
592                     final long videosSize = totalValues(details, userId,
593                             Environment.DIRECTORY_MOVIES);
594                     updatePreference(item, videosSize);
595                     accountedSize += videosSize;
596                     if (LOGV) Log.v(TAG, "videosSize: " + videosSize
597                             + " accountedSize: " + accountedSize);
598                 } break;
599                 case R.string.storage_detail_audio: {
600                     final long audioSize = totalValues(details, userId,
601                             Environment.DIRECTORY_MUSIC,
602                             Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
603                             Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
604                     updatePreference(item, audioSize);
605                     accountedSize += audioSize;
606                     if (LOGV) Log.v(TAG, "audioSize: " + audioSize
607                             + " accountedSize: " + accountedSize);
608                 } break;
609                 case R.string.storage_detail_other: {
610                     final long downloadsSize = totalValues(details, userId,
611                             Environment.DIRECTORY_DOWNLOADS);
612                     final long miscSize = details.miscSize.get(userId);
613                     totalDownloadsSize += downloadsSize;
614                     totalMiscSize += miscSize;
615                     accountedSize += miscSize + downloadsSize;
616 
617                     if (LOGV)
618                         Log.v(TAG, "miscSize for " + userId + ": " + miscSize + "(total: "
619                                 + totalMiscSize + ") \ndownloadsSize: " + downloadsSize + "(total: "
620                                 + totalDownloadsSize + ") accountedSize: " + accountedSize);
621 
622                     // Cannot display 'Other' until all known items are accounted for.
623                     otherItem = item;
624                 } break;
625                 case R.string.storage_detail_cached: {
626                     updatePreference(item, details.cacheSize);
627                     accountedSize += details.cacheSize;
628                     if (LOGV)
629                         Log.v(TAG, "cacheSize: " + details.cacheSize + " accountedSize: "
630                                 + accountedSize);
631                 } break;
632                 case 0: {
633                     final long userSize = details.usersSize.get(userId);
634                     updatePreference(item, userSize);
635                     accountedSize += userSize;
636                     if (LOGV) Log.v(TAG, "userSize: " + userSize
637                             + " accountedSize: " + accountedSize);
638                 } break;
639             }
640         }
641         if (otherItem != null) {
642             final long usedSize = mTotalSize - details.availSize;
643             final long unaccountedSize = usedSize - accountedSize;
644             final long otherSize = totalMiscSize + totalDownloadsSize + unaccountedSize;
645             Log.v(TAG, "Other items: \n\tmTotalSize: " + mTotalSize + " availSize: "
646                     + details.availSize + " usedSize: " + usedSize + "\n\taccountedSize: "
647                     + accountedSize + " unaccountedSize size: " + unaccountedSize
648                     + "\n\ttotalMiscSize: " + totalMiscSize + " totalDownloadsSize: "
649                     + totalDownloadsSize + "\n\tdetails: " + details);
650             updatePreference(otherItem, otherSize);
651         }
652     }
653 
updatePreference(StorageItemPreference pref, long size)654     private void updatePreference(StorageItemPreference pref, long size) {
655         pref.setStorageSize(size, mTotalSize);
656     }
657 
totalValues(MeasurementDetails details, int userId, String... keys)658     private static long totalValues(MeasurementDetails details, int userId, String... keys) {
659         long total = 0;
660         HashMap<String, Long> map = details.mediaSize.get(userId);
661         if (map != null) {
662             for (String key : keys) {
663                 if (map.containsKey(key)) {
664                     total += map.get(key);
665                 }
666             }
667         } else {
668             Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
669         }
670         return total;
671     }
672 
673     private final StorageEventListener mStorageListener = new StorageEventListener() {
674         @Override
675         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
676             if (Objects.equals(mVolume.getId(), vol.getId())) {
677                 mVolume = vol;
678                 update();
679             }
680         }
681 
682         @Override
683         public void onVolumeRecordChanged(VolumeRecord rec) {
684             if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
685                 mVolume = mStorageManager.findVolumeById(mVolumeId);
686                 update();
687             }
688         }
689     };
690 
691     /**
692      * Dialog that allows editing of volume nickname.
693      */
694     public static class RenameFragment extends InstrumentedDialogFragment {
show(PrivateVolumeSettings parent, VolumeInfo vol)695         public static void show(PrivateVolumeSettings parent, VolumeInfo vol) {
696             if (!parent.isAdded()) return;
697 
698             final RenameFragment dialog = new RenameFragment();
699             dialog.setTargetFragment(parent, 0);
700             final Bundle args = new Bundle();
701             args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
702             dialog.setArguments(args);
703             dialog.show(parent.getFragmentManager(), TAG_RENAME);
704         }
705 
706         @Override
getMetricsCategory()707         public int getMetricsCategory() {
708             return MetricsEvent.DIALOG_VOLUME_RENAME;
709         }
710 
711         @Override
onCreateDialog(Bundle savedInstanceState)712         public Dialog onCreateDialog(Bundle savedInstanceState) {
713             final Context context = getActivity();
714             final StorageManager storageManager = context.getSystemService(StorageManager.class);
715 
716             final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
717             final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid);
718             final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid);
719 
720             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
721             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
722 
723             final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false);
724             final EditText nickname = (EditText) view.findViewById(R.id.edittext);
725             nickname.setText(rec.getNickname());
726 
727             builder.setTitle(R.string.storage_rename_title);
728             builder.setView(view);
729 
730             builder.setPositiveButton(R.string.save,
731                     new DialogInterface.OnClickListener() {
732                         @Override
733                         public void onClick(DialogInterface dialog, int which) {
734                             // TODO: move to background thread
735                             storageManager.setVolumeNickname(fsUuid,
736                                     nickname.getText().toString());
737                         }
738                     });
739             builder.setNegativeButton(R.string.cancel, null);
740 
741             return builder.create();
742         }
743     }
744 
745     public static class SystemInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent)746         public static void show(Fragment parent) {
747             if (!parent.isAdded()) return;
748 
749             final SystemInfoFragment dialog = new SystemInfoFragment();
750             dialog.setTargetFragment(parent, 0);
751             dialog.show(parent.getFragmentManager(), TAG_SYSTEM_INFO);
752         }
753 
754         @Override
getMetricsCategory()755         public int getMetricsCategory() {
756             return MetricsEvent.DIALOG_STORAGE_SYSTEM_INFO;
757         }
758 
759         @Override
onCreateDialog(Bundle savedInstanceState)760         public Dialog onCreateDialog(Bundle savedInstanceState) {
761             return new AlertDialog.Builder(getActivity())
762                     .setMessage(getContext().getString(R.string.storage_detail_dialog_system,
763                             Build.VERSION.RELEASE))
764                     .setPositiveButton(android.R.string.ok, null)
765                     .create();
766         }
767     }
768 
769     public static class OtherInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent, String title, VolumeInfo sharedVol, int userId)770         public static void show(Fragment parent, String title, VolumeInfo sharedVol, int userId) {
771             if (!parent.isAdded()) return;
772 
773             final OtherInfoFragment dialog = new OtherInfoFragment();
774             dialog.setTargetFragment(parent, 0);
775             final Bundle args = new Bundle();
776             args.putString(Intent.EXTRA_TITLE, title);
777 
778             final Intent intent = sharedVol.buildBrowseIntent();
779             intent.putExtra(Intent.EXTRA_USER_ID, userId);
780             args.putParcelable(Intent.EXTRA_INTENT, intent);
781             dialog.setArguments(args);
782             dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO);
783         }
784 
785         @Override
getMetricsCategory()786         public int getMetricsCategory() {
787             return MetricsEvent.DIALOG_STORAGE_OTHER_INFO;
788         }
789 
790         @Override
onCreateDialog(Bundle savedInstanceState)791         public Dialog onCreateDialog(Bundle savedInstanceState) {
792             final Context context = getActivity();
793 
794             final String title = getArguments().getString(Intent.EXTRA_TITLE);
795             final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT);
796 
797             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
798             builder.setMessage(
799                     TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title));
800 
801             builder.setPositiveButton(R.string.storage_menu_explore,
802                     new DialogInterface.OnClickListener() {
803                         @Override
804                         public void onClick(DialogInterface dialog, int which) {
805                             Utils.launchIntent(OtherInfoFragment.this, intent);
806                         }
807                     });
808             builder.setNegativeButton(android.R.string.cancel, null);
809 
810             return builder.create();
811         }
812     }
813 
814     public static class UserInfoFragment extends InstrumentedDialogFragment {
show(Fragment parent, CharSequence userLabel, CharSequence userSize)815         public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) {
816             if (!parent.isAdded()) return;
817 
818             final UserInfoFragment dialog = new UserInfoFragment();
819             dialog.setTargetFragment(parent, 0);
820             final Bundle args = new Bundle();
821             args.putCharSequence(Intent.EXTRA_TITLE, userLabel);
822             args.putCharSequence(Intent.EXTRA_SUBJECT, userSize);
823             dialog.setArguments(args);
824             dialog.show(parent.getFragmentManager(), TAG_USER_INFO);
825         }
826 
827         @Override
getMetricsCategory()828         public int getMetricsCategory() {
829             return MetricsEvent.DIALOG_STORAGE_USER_INFO;
830         }
831 
832         @Override
onCreateDialog(Bundle savedInstanceState)833         public Dialog onCreateDialog(Bundle savedInstanceState) {
834             final Context context = getActivity();
835 
836             final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE);
837             final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT);
838 
839             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
840             builder.setMessage(TextUtils.expandTemplate(
841                     getText(R.string.storage_detail_dialog_user), userLabel, userSize));
842 
843             builder.setPositiveButton(android.R.string.ok, null);
844 
845             return builder.create();
846         }
847     }
848 
849     /**
850      * Dialog to request user confirmation before clearing all cache data.
851      */
852     public static class ConfirmClearCacheFragment extends InstrumentedDialogFragment {
show(Fragment parent)853         public static void show(Fragment parent) {
854             if (!parent.isAdded()) return;
855 
856             final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
857             dialog.setTargetFragment(parent, 0);
858             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
859         }
860 
861         @Override
getMetricsCategory()862         public int getMetricsCategory() {
863             return MetricsEvent.DIALOG_STORAGE_CLEAR_CACHE;
864         }
865 
866         @Override
onCreateDialog(Bundle savedInstanceState)867         public Dialog onCreateDialog(Bundle savedInstanceState) {
868             final Context context = getActivity();
869 
870             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
871             builder.setTitle(R.string.memory_clear_cache_title);
872             builder.setMessage(getString(R.string.memory_clear_cache_message));
873 
874             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
875                 @Override
876                 public void onClick(DialogInterface dialog, int which) {
877                     final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment();
878                     final PackageManager pm = context.getPackageManager();
879                     final UserManager um = context.getSystemService(UserManager.class);
880 
881                     for (int userId : um.getProfileIdsWithDisabled(context.getUserId())) {
882                         final List<PackageInfo> infos = pm.getInstalledPackagesAsUser(0, userId);
883                         final ClearCacheObserver observer = new ClearCacheObserver(
884                                 target, infos.size());
885                         for (PackageInfo info : infos) {
886                             pm.deleteApplicationCacheFilesAsUser(info.packageName, userId,
887                                     observer);
888                         }
889                     }
890                 }
891             });
892             builder.setNegativeButton(android.R.string.cancel, null);
893 
894             return builder.create();
895         }
896     }
897 
898     private static class ClearCacheObserver extends IPackageDataObserver.Stub {
899         private final PrivateVolumeSettings mTarget;
900         private int mRemaining;
901 
ClearCacheObserver(PrivateVolumeSettings target, int remaining)902         public ClearCacheObserver(PrivateVolumeSettings target, int remaining) {
903             mTarget = target;
904             mRemaining = remaining;
905         }
906 
907         @Override
onRemoveCompleted(final String packageName, final boolean succeeded)908         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
909             synchronized (this) {
910                 if (--mRemaining == 0) {
911                     mTarget.getActivity().runOnUiThread(new Runnable() {
912                         @Override
913                         public void run() {
914                             mTarget.update();
915                         }
916                     });
917                 }
918             }
919         }
920     }
921 }
922