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