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.DialogFragment; 22import android.app.Fragment; 23import android.content.ActivityNotFoundException; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.Intent; 27import android.content.pm.IPackageDataObserver; 28import android.content.pm.PackageInfo; 29import android.content.pm.PackageManager; 30import android.content.pm.UserInfo; 31import android.os.Bundle; 32import android.os.Environment; 33import android.os.UserHandle; 34import android.os.UserManager; 35import android.os.storage.StorageEventListener; 36import android.os.storage.StorageManager; 37import android.os.storage.VolumeInfo; 38import android.os.storage.VolumeRecord; 39import android.provider.DocumentsContract; 40import android.provider.Settings; 41import android.support.v7.preference.Preference; 42import android.support.v7.preference.PreferenceCategory; 43import android.support.v7.preference.PreferenceGroup; 44import android.support.v7.preference.PreferenceScreen; 45import android.text.TextUtils; 46import android.text.format.Formatter; 47import android.text.format.Formatter.BytesResult; 48import android.util.Log; 49import android.view.LayoutInflater; 50import android.view.Menu; 51import android.view.MenuInflater; 52import android.view.MenuItem; 53import android.view.View; 54import android.widget.EditText; 55 56import com.android.internal.logging.MetricsProto.MetricsEvent; 57import com.android.settings.R; 58import com.android.settings.Settings.StorageUseActivity; 59import com.android.settings.SettingsPreferenceFragment; 60import com.android.settings.Utils; 61import com.android.settings.applications.ManageApplications; 62import com.android.settings.deletionhelper.AutomaticStorageManagerSettings; 63import com.android.settings.deviceinfo.StorageSettings.MountTask; 64import com.android.settingslib.deviceinfo.StorageMeasurement; 65import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails; 66import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementReceiver; 67import com.google.android.collect.Lists; 68 69import java.io.File; 70import java.util.HashMap; 71import java.util.List; 72import java.util.Objects; 73 74/** 75 * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE} 76 * storage volume. 77 */ 78public 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 136 private boolean isVolumeValid() { 137 return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE) 138 && mVolume.isMountedReadable(); 139 } 140 141 public PrivateVolumeSettings() { 142 setRetainInstance(true); 143 } 144 145 @Override 146 protected int getMetricsCategory() { 147 return MetricsEvent.DEVICEINFO_STORAGE; 148 } 149 150 @Override 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 198 private void setTitle() { 199 getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume)); 200 } 201 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 277 private void addPreference(PreferenceGroup group, Preference pref) { 278 pref.setOrder(Preference.DEFAULT_ORDER); 279 group.addPreference(pref); 280 } 281 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 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 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 336 private StorageItemPreference buildItem() { 337 final StorageItemPreference item = new StorageItemPreference(getPrefContext()); 338 return item; 339 } 340 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 348 static void setVolumeSize(Bundle args, long size) { 349 args.putLong(EXTRA_VOLUME_SIZE, size); 350 } 351 352 @Override 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 373 public void onPause() { 374 super.onPause(); 375 mStorageManager.unregisterListener(mStorageListener); 376 } 377 378 @Override 379 public void onDestroy() { 380 super.onDestroy(); 381 if (mMeasure != null) { 382 mMeasure.onDestroy(); 383 } 384 } 385 386 @Override 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 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 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 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 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 654 private void updatePreference(StorageItemPreference pref, long size) { 655 pref.setStorageSize(size, mTotalSize); 656 } 657 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 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 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 { 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 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 { 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 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 { 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 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 { 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 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 { 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 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 896 public ClearCacheObserver(PrivateVolumeSettings target, int remaining) { 897 mTarget = target; 898 mRemaining = remaining; 899 } 900 901 @Override 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