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