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 int i = item.getItemId(); 428 if (i == R.id.storage_rename) { 429 RenameFragment.show(this, mVolume); 430 return true; 431 } else if (i == R.id.storage_mount) { 432 new MountTask(context, mVolume).execute(); 433 return true; 434 } else if (i == 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 } else if (i == 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 } else if (i == 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 } else if (i == 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 if (itemTitleId == 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 args.putInt( 486 ManageApplications.EXTRA_STORAGE_TYPE, 487 ManageApplications.STORAGE_TYPE_LEGACY); 488 intent = new SubSettingLauncher(getActivity()) 489 .setDestination(ManageApplications.class.getName()) 490 .setArguments(args) 491 .setTitleRes(R.string.apps_storage) 492 .setSourceMetricsCategory(getMetricsCategory()) 493 .toIntent(); 494 } else if (itemTitleId == R.string.storage_detail_images) { 495 intent = getIntentForStorage(AUTHORITY_MEDIA, "images_root"); 496 } else if (itemTitleId == R.string.storage_detail_videos) { 497 intent = getIntentForStorage(AUTHORITY_MEDIA, "videos_root"); 498 } else if (itemTitleId == R.string.storage_detail_audio) { 499 intent = getIntentForStorage(AUTHORITY_MEDIA, "audio_root"); 500 } else if (itemTitleId == R.string.storage_detail_system) { 501 SystemInfoFragment.show(this); 502 return true; 503 } else if (itemTitleId == R.string.storage_detail_other) { 504 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume), 505 mSharedVolume, userId); 506 return true; 507 } else if (itemTitleId == R.string.storage_detail_cached) { 508 ConfirmClearCacheFragment.show(this); 509 return true; 510 } else if (itemTitleId == R.string.storage_menu_explore) { 511 intent = mSharedVolume.buildBrowseIntent(); 512 } else if (itemTitleId == 0) { 513 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary()); 514 return true; 515 } 516 517 if (intent != null) { 518 intent.putExtra(Intent.EXTRA_USER_ID, userId); 519 520 Utils.launchIntent(this, intent); 521 return true; 522 } 523 return super.onPreferenceTreeClick(pref); 524 } 525 getIntentForStorage(String authority, String root)526 private Intent getIntentForStorage(String authority, String root) { 527 Intent intent = new Intent(Intent.ACTION_VIEW); 528 intent.setDataAndType( 529 DocumentsContract.buildRootUri(authority, root), 530 DocumentsContract.Root.MIME_TYPE_ITEM); 531 intent.addCategory(Intent.CATEGORY_DEFAULT); 532 533 return intent; 534 } 535 536 private final MeasurementReceiver mReceiver = new MeasurementReceiver() { 537 @Override 538 public void onDetailsChanged(MeasurementDetails details) { 539 updateDetails(details); 540 } 541 }; 542 updateDetails(MeasurementDetails details)543 private void updateDetails(MeasurementDetails details) { 544 StorageItemPreference otherItem = null; 545 long accountedSize = 0; 546 long totalMiscSize = 0; 547 long totalDownloadsSize = 0; 548 549 for (int i = 0; i < mItemPoolIndex; ++i) { 550 StorageItemPreference item = mItemPreferencePool.get(i); 551 final int userId = item.userHandle; 552 int itemTitleId; 553 try { 554 itemTitleId = Integer.parseInt(item.getKey()); 555 } catch (NumberFormatException e) { 556 itemTitleId = 0; 557 } 558 // Cannot display 'Other' until all known items are accounted for. 559 if (itemTitleId == R.string.storage_detail_system) { 560 updatePreference(item, mSystemSize); 561 accountedSize += mSystemSize; 562 if (LOGV) Log.v(TAG, "mSystemSize: " + mSystemSize 563 + " accountedSize: " + accountedSize); 564 } else if (itemTitleId == R.string.storage_detail_apps) { 565 updatePreference(item, details.appsSize.get(userId)); 566 accountedSize += details.appsSize.get(userId); 567 if (LOGV) Log.v(TAG, "appsSize: " + details.appsSize.get(userId) 568 + " accountedSize: " + accountedSize); 569 } else if (itemTitleId == R.string.storage_detail_images) { 570 final long imagesSize = totalValues(details, userId, 571 Environment.DIRECTORY_DCIM, Environment.DIRECTORY_PICTURES); 572 updatePreference(item, imagesSize); 573 accountedSize += imagesSize; 574 if (LOGV) Log.v(TAG, "imagesSize: " + imagesSize 575 + " accountedSize: " + accountedSize); 576 } else if (itemTitleId == R.string.storage_detail_videos) { 577 final long videosSize = totalValues(details, userId, 578 Environment.DIRECTORY_MOVIES); 579 updatePreference(item, videosSize); 580 accountedSize += videosSize; 581 if (LOGV) Log.v(TAG, "videosSize: " + videosSize 582 + " accountedSize: " + accountedSize); 583 } else if (itemTitleId == R.string.storage_detail_audio) { 584 final long audioSize = totalValues(details, userId, 585 Environment.DIRECTORY_MUSIC, 586 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, 587 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); 588 updatePreference(item, audioSize); 589 accountedSize += audioSize; 590 if (LOGV) Log.v(TAG, "audioSize: " + audioSize 591 + " accountedSize: " + accountedSize); 592 } else if (itemTitleId == R.string.storage_detail_other) { 593 final long downloadsSize = totalValues(details, userId, 594 Environment.DIRECTORY_DOWNLOADS); 595 final long miscSize = details.miscSize.get(userId); 596 totalDownloadsSize += downloadsSize; 597 totalMiscSize += miscSize; 598 accountedSize += miscSize + downloadsSize; 599 if (LOGV) 600 Log.v(TAG, "miscSize for " + userId + ": " + miscSize + "(total: " 601 + totalMiscSize + ") \ndownloadsSize: " + downloadsSize + "(total: " 602 + totalDownloadsSize + ") accountedSize: " + accountedSize); 603 otherItem = item; 604 } else if (itemTitleId == R.string.storage_detail_cached) { 605 updatePreference(item, details.cacheSize); 606 accountedSize += details.cacheSize; 607 if (LOGV) 608 Log.v(TAG, "cacheSize: " + details.cacheSize + " accountedSize: " 609 + accountedSize); 610 } else if (itemTitleId == 0) { 611 final long userSize = details.usersSize.get(userId); 612 updatePreference(item, userSize); 613 accountedSize += userSize; 614 if (LOGV) Log.v(TAG, "userSize: " + userSize 615 + " accountedSize: " + accountedSize); 616 } 617 } 618 if (otherItem != null) { 619 final long usedSize = mTotalSize - details.availSize; 620 final long unaccountedSize = usedSize - accountedSize; 621 final long otherSize = totalMiscSize + totalDownloadsSize + unaccountedSize; 622 Log.v(TAG, "Other items: \n\tmTotalSize: " + mTotalSize + " availSize: " 623 + details.availSize + " usedSize: " + usedSize + "\n\taccountedSize: " 624 + accountedSize + " unaccountedSize size: " + unaccountedSize 625 + "\n\ttotalMiscSize: " + totalMiscSize + " totalDownloadsSize: " 626 + totalDownloadsSize + "\n\tdetails: " + details); 627 updatePreference(otherItem, otherSize); 628 } 629 } 630 updatePreference(StorageItemPreference pref, long size)631 private void updatePreference(StorageItemPreference pref, long size) { 632 pref.setStorageSize(size, mTotalSize); 633 } 634 totalValues(MeasurementDetails details, int userId, String... keys)635 private static long totalValues(MeasurementDetails details, int userId, String... keys) { 636 long total = 0; 637 HashMap<String, Long> map = details.mediaSize.get(userId); 638 if (map != null) { 639 for (String key : keys) { 640 if (map.containsKey(key)) { 641 total += map.get(key); 642 } 643 } 644 } else { 645 Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId); 646 } 647 return total; 648 } 649 650 private final StorageEventListener mStorageListener = new StorageEventListener() { 651 @Override 652 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 653 if (Objects.equals(mVolume.getId(), vol.getId())) { 654 mVolume = vol; 655 update(); 656 } 657 } 658 659 @Override 660 public void onVolumeRecordChanged(VolumeRecord rec) { 661 if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) { 662 mVolume = mStorageManager.findVolumeById(mVolumeId); 663 update(); 664 } 665 } 666 }; 667 668 /** 669 * Dialog that allows editing of volume nickname. 670 */ 671 public static class RenameFragment extends InstrumentedDialogFragment { show(PrivateVolumeSettings parent, VolumeInfo vol)672 public static void show(PrivateVolumeSettings parent, VolumeInfo vol) { 673 if (!parent.isAdded()) return; 674 675 final RenameFragment dialog = new RenameFragment(); 676 dialog.setTargetFragment(parent, 0); 677 final Bundle args = new Bundle(); 678 args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid()); 679 dialog.setArguments(args); 680 dialog.show(parent.getFragmentManager(), TAG_RENAME); 681 } 682 683 @Override getMetricsCategory()684 public int getMetricsCategory() { 685 return SettingsEnums.DIALOG_VOLUME_RENAME; 686 } 687 688 @Override onCreateDialog(Bundle savedInstanceState)689 public Dialog onCreateDialog(Bundle savedInstanceState) { 690 final Context context = getActivity(); 691 final StorageManager storageManager = context.getSystemService(StorageManager.class); 692 693 final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID); 694 final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid); 695 final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid); 696 697 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 698 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 699 700 final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false); 701 final EditText nickname = (EditText) view.findViewById(R.id.edittext); 702 nickname.setText(rec.getNickname()); 703 704 builder.setTitle(R.string.storage_rename_title); 705 builder.setView(view); 706 707 builder.setPositiveButton(R.string.save, 708 new DialogInterface.OnClickListener() { 709 @Override 710 public void onClick(DialogInterface dialog, int which) { 711 // TODO: move to background thread 712 storageManager.setVolumeNickname(fsUuid, 713 nickname.getText().toString()); 714 } 715 }); 716 builder.setNegativeButton(R.string.cancel, null); 717 718 return builder.create(); 719 } 720 } 721 722 public static class SystemInfoFragment extends InstrumentedDialogFragment { show(Fragment parent)723 public static void show(Fragment parent) { 724 if (!parent.isAdded()) return; 725 726 final SystemInfoFragment dialog = new SystemInfoFragment(); 727 dialog.setTargetFragment(parent, 0); 728 dialog.show(parent.getFragmentManager(), TAG_SYSTEM_INFO); 729 } 730 731 @Override getMetricsCategory()732 public int getMetricsCategory() { 733 return SettingsEnums.DIALOG_STORAGE_SYSTEM_INFO; 734 } 735 736 @Override onCreateDialog(Bundle savedInstanceState)737 public Dialog onCreateDialog(Bundle savedInstanceState) { 738 return new AlertDialog.Builder(getActivity()) 739 .setMessage(getContext().getString(R.string.storage_detail_dialog_system, 740 Build.VERSION.RELEASE)) 741 .setPositiveButton(android.R.string.ok, null) 742 .create(); 743 } 744 } 745 746 public static class OtherInfoFragment extends InstrumentedDialogFragment { show(Fragment parent, String title, VolumeInfo sharedVol, int userId)747 public static void show(Fragment parent, String title, VolumeInfo sharedVol, int userId) { 748 if (!parent.isAdded()) return; 749 750 final OtherInfoFragment dialog = new OtherInfoFragment(); 751 dialog.setTargetFragment(parent, 0); 752 final Bundle args = new Bundle(); 753 args.putString(Intent.EXTRA_TITLE, title); 754 755 final Intent intent = sharedVol.buildBrowseIntent(); 756 intent.putExtra(Intent.EXTRA_USER_ID, userId); 757 args.putParcelable(Intent.EXTRA_INTENT, intent); 758 dialog.setArguments(args); 759 dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO); 760 } 761 762 @Override getMetricsCategory()763 public int getMetricsCategory() { 764 return SettingsEnums.DIALOG_STORAGE_OTHER_INFO; 765 } 766 767 @Override onCreateDialog(Bundle savedInstanceState)768 public Dialog onCreateDialog(Bundle savedInstanceState) { 769 final Context context = getActivity(); 770 771 final String title = getArguments().getString(Intent.EXTRA_TITLE); 772 final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT); 773 774 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 775 builder.setMessage( 776 TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title)); 777 778 builder.setPositiveButton(R.string.storage_menu_explore, 779 new DialogInterface.OnClickListener() { 780 @Override 781 public void onClick(DialogInterface dialog, int which) { 782 Utils.launchIntent(OtherInfoFragment.this, intent); 783 } 784 }); 785 builder.setNegativeButton(android.R.string.cancel, null); 786 787 return builder.create(); 788 } 789 } 790 791 public static class UserInfoFragment extends InstrumentedDialogFragment { show(Fragment parent, CharSequence userLabel, CharSequence userSize)792 public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) { 793 if (!parent.isAdded()) return; 794 795 final UserInfoFragment dialog = new UserInfoFragment(); 796 dialog.setTargetFragment(parent, 0); 797 final Bundle args = new Bundle(); 798 args.putCharSequence(Intent.EXTRA_TITLE, userLabel); 799 args.putCharSequence(Intent.EXTRA_SUBJECT, userSize); 800 dialog.setArguments(args); 801 dialog.show(parent.getFragmentManager(), TAG_USER_INFO); 802 } 803 804 @Override getMetricsCategory()805 public int getMetricsCategory() { 806 return SettingsEnums.DIALOG_STORAGE_USER_INFO; 807 } 808 809 @Override onCreateDialog(Bundle savedInstanceState)810 public Dialog onCreateDialog(Bundle savedInstanceState) { 811 final Context context = getActivity(); 812 813 final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE); 814 final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT); 815 816 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 817 builder.setMessage(TextUtils.expandTemplate( 818 getText(R.string.storage_detail_dialog_user), userLabel, userSize)); 819 820 builder.setPositiveButton(android.R.string.ok, null); 821 822 return builder.create(); 823 } 824 } 825 826 /** 827 * Dialog to request user confirmation before clearing all cache data. 828 */ 829 public static class ConfirmClearCacheFragment extends InstrumentedDialogFragment { show(Fragment parent)830 public static void show(Fragment parent) { 831 if (!parent.isAdded()) return; 832 833 final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment(); 834 dialog.setTargetFragment(parent, 0); 835 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE); 836 } 837 838 @Override getMetricsCategory()839 public int getMetricsCategory() { 840 return SettingsEnums.DIALOG_STORAGE_CLEAR_CACHE; 841 } 842 843 @Override onCreateDialog(Bundle savedInstanceState)844 public Dialog onCreateDialog(Bundle savedInstanceState) { 845 final Context context = getActivity(); 846 847 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 848 builder.setTitle(R.string.memory_clear_cache_title); 849 builder.setMessage(getString(R.string.memory_clear_cache_message)); 850 851 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 852 @Override 853 public void onClick(DialogInterface dialog, int which) { 854 final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment(); 855 final PackageManager pm = context.getPackageManager(); 856 final UserManager um = context.getSystemService(UserManager.class); 857 858 for (int userId : um.getProfileIdsWithDisabled(context.getUserId())) { 859 final List<PackageInfo> infos = pm.getInstalledPackagesAsUser(0, userId); 860 final ClearCacheObserver observer = new ClearCacheObserver( 861 target, infos.size()); 862 for (PackageInfo info : infos) { 863 pm.deleteApplicationCacheFilesAsUser(info.packageName, userId, 864 observer); 865 } 866 } 867 } 868 }); 869 builder.setNegativeButton(android.R.string.cancel, null); 870 871 return builder.create(); 872 } 873 } 874 875 private static class ClearCacheObserver extends IPackageDataObserver.Stub { 876 private final PrivateVolumeSettings mTarget; 877 private int mRemaining; 878 ClearCacheObserver(PrivateVolumeSettings target, int remaining)879 public ClearCacheObserver(PrivateVolumeSettings target, int remaining) { 880 mTarget = target; 881 mRemaining = remaining; 882 } 883 884 @Override onRemoveCompleted(final String packageName, final boolean succeeded)885 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 886 synchronized (this) { 887 if (--mRemaining == 0) { 888 mTarget.getActivity().runOnUiThread(new Runnable() { 889 @Override 890 public void run() { 891 mTarget.update(); 892 } 893 }); 894 } 895 } 896 } 897 } 898 } 899