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