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