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