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 static com.android.settings.deviceinfo.StorageSettings.TAG; 20 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.DialogFragment; 24 import android.app.Fragment; 25 import android.content.ActivityNotFoundException; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.content.pm.IPackageDataObserver; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.UserInfo; 33 import android.os.Bundle; 34 import android.os.Environment; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.os.storage.StorageEventListener; 38 import android.os.storage.StorageManager; 39 import android.os.storage.VolumeInfo; 40 import android.os.storage.VolumeRecord; 41 import android.preference.Preference; 42 import android.preference.PreferenceCategory; 43 import android.preference.PreferenceGroup; 44 import android.preference.PreferenceScreen; 45 import android.provider.DocumentsContract; 46 import android.text.TextUtils; 47 import android.text.format.Formatter; 48 import android.text.format.Formatter.BytesResult; 49 import android.util.Log; 50 import android.view.LayoutInflater; 51 import android.view.Menu; 52 import android.view.MenuInflater; 53 import android.view.MenuItem; 54 import android.view.View; 55 import android.widget.EditText; 56 57 import com.android.internal.logging.MetricsLogger; 58 import com.android.settings.R; 59 import com.android.settings.Settings.StorageUseActivity; 60 import com.android.settings.SettingsPreferenceFragment; 61 import com.android.settings.Utils; 62 import com.android.settings.applications.ManageApplications; 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 import com.google.android.collect.Lists; 68 69 import java.io.File; 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_RENAME = "rename"; 83 private static final String TAG_OTHER_INFO = "otherInfo"; 84 private static final String TAG_USER_INFO = "userInfo"; 85 private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache"; 86 87 private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents"; 88 89 private static final int[] ITEMS_NO_SHOW_SHARED = new int[] { 90 R.string.storage_detail_apps, 91 }; 92 93 private static final int[] ITEMS_SHOW_SHARED = new int[] { 94 R.string.storage_detail_apps, 95 R.string.storage_detail_images, 96 R.string.storage_detail_videos, 97 R.string.storage_detail_audio, 98 R.string.storage_detail_other 99 }; 100 101 private StorageManager mStorageManager; 102 private UserManager mUserManager; 103 104 private String mVolumeId; 105 private VolumeInfo mVolume; 106 private VolumeInfo mSharedVolume; 107 108 private StorageMeasurement mMeasure; 109 110 private UserInfo mCurrentUser; 111 112 private StorageSummaryPreference mSummary; 113 private List<StorageItemPreference> mItemPreferencePool = Lists.newArrayList(); 114 private List<PreferenceCategory> mHeaderPreferencePool = Lists.newArrayList(); 115 private int mHeaderPoolIndex; 116 private int mItemPoolIndex; 117 118 private Preference mExplore; 119 120 private boolean isVolumeValid() { 121 return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE) 122 && mVolume.isMountedReadable(); 123 } 124 125 @Override 126 protected int getMetricsCategory() { 127 return MetricsLogger.DEVICEINFO_STORAGE; 128 } 129 130 @Override 131 public void onCreate(Bundle icicle) { 132 super.onCreate(icicle); 133 134 final Context context = getActivity(); 135 136 mUserManager = context.getSystemService(UserManager.class); 137 mStorageManager = context.getSystemService(StorageManager.class); 138 139 mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID); 140 mVolume = mStorageManager.findVolumeById(mVolumeId); 141 142 // Find the emulated shared storage layered above this private volume 143 mSharedVolume = mStorageManager.findEmulatedForPrivate(mVolume); 144 145 mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume); 146 mMeasure.setReceiver(mReceiver); 147 148 if (!isVolumeValid()) { 149 getActivity().finish(); 150 return; 151 } 152 153 addPreferencesFromResource(R.xml.device_info_storage_volume); 154 getPreferenceScreen().setOrderingAsAdded(true); 155 156 mSummary = new StorageSummaryPreference(context); 157 mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId()); 158 159 mExplore = buildAction(R.string.storage_menu_explore); 160 161 setHasOptionsMenu(true); 162 } 163 164 public void update() { 165 if (!isVolumeValid()) { 166 getActivity().finish(); 167 return; 168 } 169 170 getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume)); 171 172 // Valid options may have changed 173 getFragmentManager().invalidateOptionsMenu(); 174 175 final Context context = getActivity(); 176 final PreferenceScreen screen = getPreferenceScreen(); 177 178 screen.removeAll(); 179 180 addPreference(screen, mSummary); 181 182 List<UserInfo> allUsers = mUserManager.getUsers(); 183 final int userCount = allUsers.size(); 184 final boolean showHeaders = userCount > 1; 185 final boolean showShared = (mSharedVolume != null) && mSharedVolume.isMountedReadable(); 186 187 mItemPoolIndex = 0; 188 mHeaderPoolIndex = 0; 189 190 int addedUserCount = 0; 191 // Add current user and its profiles first 192 for (int userIndex = 0; userIndex < userCount; ++userIndex) { 193 final UserInfo userInfo = allUsers.get(userIndex); 194 if (isProfileOf(mCurrentUser, userInfo)) { 195 final PreferenceGroup details = showHeaders ? 196 addCategory(screen, userInfo.name) : screen; 197 addDetailItems(details, showShared, userInfo.id); 198 ++addedUserCount; 199 } 200 } 201 202 // Add rest of users 203 if (userCount - addedUserCount > 0) { 204 PreferenceGroup otherUsers = addCategory(screen, 205 getText(R.string.storage_other_users)); 206 for (int userIndex = 0; userIndex < userCount; ++userIndex) { 207 final UserInfo userInfo = allUsers.get(userIndex); 208 if (!isProfileOf(mCurrentUser, userInfo)) { 209 addItem(otherUsers, /* titleRes */ 0, userInfo.name, userInfo.id); 210 } 211 } 212 } 213 214 addItem(screen, R.string.storage_detail_cached, null, UserHandle.USER_NULL); 215 216 if (showShared) { 217 addPreference(screen, mExplore); 218 } 219 220 final File file = mVolume.getPath(); 221 final long totalBytes = file.getTotalSpace(); 222 final long freeBytes = file.getFreeSpace(); 223 final long usedBytes = totalBytes - freeBytes; 224 225 final BytesResult result = Formatter.formatBytes(getResources(), usedBytes, 0); 226 mSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large), 227 result.value, result.units)); 228 mSummary.setSummary(getString(R.string.storage_volume_used, 229 Formatter.formatFileSize(context, totalBytes))); 230 mSummary.setPercent((int) ((usedBytes * 100) / totalBytes)); 231 232 mMeasure.forceMeasure(); 233 } 234 235 private void addPreference(PreferenceGroup group, Preference pref) { 236 pref.setOrder(Preference.DEFAULT_ORDER); 237 group.addPreference(pref); 238 } 239 240 private PreferenceCategory addCategory(PreferenceGroup group, CharSequence title) { 241 PreferenceCategory category; 242 if (mHeaderPoolIndex < mHeaderPreferencePool.size()) { 243 category = mHeaderPreferencePool.get(mHeaderPoolIndex); 244 } else { 245 category = new PreferenceCategory(getActivity(), null, 246 com.android.internal.R.attr.preferenceCategoryStyle); 247 mHeaderPreferencePool.add(category); 248 } 249 category.setTitle(title); 250 category.removeAll(); 251 addPreference(group, category); 252 ++mHeaderPoolIndex; 253 return category; 254 } 255 256 private void addDetailItems(PreferenceGroup category, boolean showShared, int userId) { 257 final int[] itemsToAdd = (showShared ? ITEMS_SHOW_SHARED : ITEMS_NO_SHOW_SHARED); 258 for (int i = 0; i < itemsToAdd.length; ++i) { 259 addItem(category, itemsToAdd[i], null, userId); 260 } 261 } 262 263 private void addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId) { 264 StorageItemPreference item; 265 if (mItemPoolIndex < mItemPreferencePool.size()) { 266 item = mItemPreferencePool.get(mItemPoolIndex); 267 } else { 268 item = buildItem(); 269 mItemPreferencePool.add(item); 270 } 271 if (title != null) { 272 item.setTitle(title); 273 } else { 274 item.setTitle(titleRes); 275 } 276 item.setSummary(R.string.memory_calculating_size); 277 item.userHandle = userId; 278 addPreference(group, item); 279 ++mItemPoolIndex; 280 } 281 282 private StorageItemPreference buildItem() { 283 final StorageItemPreference item = new StorageItemPreference(getActivity()); 284 return item; 285 } 286 287 private Preference buildAction(int titleRes) { 288 final Preference pref = new Preference(getActivity()); 289 pref.setTitle(titleRes); 290 return pref; 291 } 292 293 @Override 294 public void onResume() { 295 super.onResume(); 296 297 // Refresh to verify that we haven't been formatted away 298 mVolume = mStorageManager.findVolumeById(mVolumeId); 299 if (!isVolumeValid()) { 300 getActivity().finish(); 301 return; 302 } 303 304 mStorageManager.registerListener(mStorageListener); 305 update(); 306 } 307 308 @Override 309 public void onPause() { 310 super.onPause(); 311 mStorageManager.unregisterListener(mStorageListener); 312 } 313 314 @Override 315 public void onDestroy() { 316 super.onDestroy(); 317 if (mMeasure != null) { 318 mMeasure.onDestroy(); 319 } 320 } 321 322 @Override 323 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 324 super.onCreateOptionsMenu(menu, inflater); 325 inflater.inflate(R.menu.storage_volume, menu); 326 } 327 328 @Override 329 public void onPrepareOptionsMenu(Menu menu) { 330 if (!isVolumeValid()) return; 331 332 final MenuItem rename = menu.findItem(R.id.storage_rename); 333 final MenuItem mount = menu.findItem(R.id.storage_mount); 334 final MenuItem unmount = menu.findItem(R.id.storage_unmount); 335 final MenuItem format = menu.findItem(R.id.storage_format); 336 final MenuItem migrate = menu.findItem(R.id.storage_migrate); 337 338 // Actions live in menu for non-internal private volumes; they're shown 339 // as preference items for public volumes. 340 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.getId())) { 341 rename.setVisible(false); 342 mount.setVisible(false); 343 unmount.setVisible(false); 344 format.setVisible(false); 345 } else { 346 rename.setVisible(mVolume.getType() == VolumeInfo.TYPE_PRIVATE); 347 mount.setVisible(mVolume.getState() == VolumeInfo.STATE_UNMOUNTED); 348 unmount.setVisible(mVolume.isMountedReadable()); 349 format.setVisible(true); 350 } 351 352 format.setTitle(R.string.storage_menu_format_public); 353 354 // Only offer to migrate when not current storage 355 final VolumeInfo privateVol = getActivity().getPackageManager() 356 .getPrimaryStorageCurrentVolume(); 357 migrate.setVisible((privateVol != null) 358 && (privateVol.getType() == VolumeInfo.TYPE_PRIVATE) 359 && !Objects.equals(mVolume, privateVol)); 360 } 361 362 @Override 363 public boolean onOptionsItemSelected(MenuItem item) { 364 final Context context = getActivity(); 365 final Bundle args = new Bundle(); 366 switch (item.getItemId()) { 367 case R.id.storage_rename: 368 RenameFragment.show(this, mVolume); 369 return true; 370 case R.id.storage_mount: 371 new MountTask(context, mVolume).execute(); 372 return true; 373 case R.id.storage_unmount: 374 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId()); 375 startFragment(this, PrivateVolumeUnmount.class.getCanonicalName(), 376 R.string.storage_menu_unmount, 0, args); 377 return true; 378 case R.id.storage_format: 379 args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId()); 380 startFragment(this, PrivateVolumeFormat.class.getCanonicalName(), 381 R.string.storage_menu_format, 0, args); 382 return true; 383 case R.id.storage_migrate: 384 final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class); 385 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId()); 386 startActivity(intent); 387 return true; 388 } 389 return super.onOptionsItemSelected(item); 390 } 391 392 @Override 393 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) { 394 // TODO: launch better intents for specific volume 395 396 final int userId = (pref instanceof StorageItemPreference ? 397 ((StorageItemPreference)pref).userHandle : -1); 398 final int itemTitleId = pref.getTitleRes(); 399 Intent intent = null; 400 switch (itemTitleId) { 401 case R.string.storage_detail_apps: { 402 Bundle args = new Bundle(); 403 args.putString(ManageApplications.EXTRA_CLASSNAME, 404 StorageUseActivity.class.getName()); 405 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid()); 406 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription()); 407 intent = Utils.onBuildStartFragmentIntent(getActivity(), 408 ManageApplications.class.getName(), args, null, R.string.apps_storage, null, 409 false); 410 411 } break; 412 case R.string.storage_detail_images: { 413 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 414 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root")); 415 intent.addCategory(Intent.CATEGORY_DEFAULT); 416 417 } break; 418 case R.string.storage_detail_videos: { 419 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 420 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "videos_root")); 421 intent.addCategory(Intent.CATEGORY_DEFAULT); 422 423 } break; 424 case R.string.storage_detail_audio: { 425 intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 426 intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root")); 427 intent.addCategory(Intent.CATEGORY_DEFAULT); 428 429 } break; 430 case R.string.storage_detail_other: { 431 OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume), 432 mSharedVolume); 433 return true; 434 435 } 436 case R.string.storage_detail_cached: { 437 ConfirmClearCacheFragment.show(this); 438 return true; 439 440 } 441 case R.string.storage_menu_explore: { 442 intent = mSharedVolume.buildBrowseIntent(); 443 } break; 444 case 0: { 445 UserInfoFragment.show(this, pref.getTitle(), pref.getSummary()); 446 return true; 447 } 448 } 449 450 if (intent != null) { 451 try { 452 if (userId == -1) { 453 startActivity(intent); 454 } else { 455 getActivity().startActivityAsUser(intent, new UserHandle(userId)); 456 } 457 } catch (ActivityNotFoundException e) { 458 Log.w(TAG, "No activity found for " + intent); 459 } 460 return true; 461 } 462 return super.onPreferenceTreeClick(preferenceScreen, pref); 463 } 464 465 private final MeasurementReceiver mReceiver = new MeasurementReceiver() { 466 @Override 467 public void onDetailsChanged(MeasurementDetails details) { 468 updateDetails(details); 469 } 470 }; 471 472 private void updateDetails(MeasurementDetails details) { 473 for (int i = 0; i < mItemPoolIndex; ++i) { 474 StorageItemPreference item = mItemPreferencePool.get(i); 475 final int userId = item.userHandle; 476 final int itemTitleId = item.getTitleRes(); 477 switch (itemTitleId) { 478 case R.string.storage_detail_apps: { 479 updatePreference(item, details.appsSize.get(userId)); 480 } break; 481 case R.string.storage_detail_images: { 482 final long imagesSize = totalValues(details, userId, 483 Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES, 484 Environment.DIRECTORY_PICTURES); 485 updatePreference(item, imagesSize); 486 } break; 487 case R.string.storage_detail_videos: { 488 final long videosSize = totalValues(details, userId, 489 Environment.DIRECTORY_MOVIES); 490 updatePreference(item, videosSize); 491 } break; 492 case R.string.storage_detail_audio: { 493 final long audioSize = totalValues(details, userId, 494 Environment.DIRECTORY_MUSIC, 495 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, 496 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); 497 updatePreference(item, audioSize); 498 } break; 499 case R.string.storage_detail_other: { 500 updatePreference(item, details.miscSize.get(userId)); 501 } break; 502 case R.string.storage_detail_cached: { 503 updatePreference(item, details.cacheSize); 504 } break; 505 case 0: { 506 final long userSize = details.usersSize.get(userId); 507 updatePreference(item, userSize); 508 } break; 509 } 510 } 511 } 512 513 private void updatePreference(StorageItemPreference pref, long size) { 514 pref.setSummary(Formatter.formatFileSize(getActivity(), size)); 515 } 516 517 private boolean isProfileOf(UserInfo user, UserInfo profile) { 518 return user.id == profile.id || 519 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 520 && user.profileGroupId == profile.profileGroupId); 521 } 522 523 private static long totalValues(MeasurementDetails details, int userId, String... keys) { 524 long total = 0; 525 HashMap<String, Long> map = details.mediaSize.get(userId); 526 if (map != null) { 527 for (String key : keys) { 528 if (map.containsKey(key)) { 529 total += map.get(key); 530 } 531 } 532 } else { 533 Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId); 534 } 535 return total; 536 } 537 538 private final StorageEventListener mStorageListener = new StorageEventListener() { 539 @Override 540 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 541 if (Objects.equals(mVolume.getId(), vol.getId())) { 542 mVolume = vol; 543 update(); 544 } 545 } 546 547 @Override 548 public void onVolumeRecordChanged(VolumeRecord rec) { 549 if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) { 550 mVolume = mStorageManager.findVolumeById(mVolumeId); 551 update(); 552 } 553 } 554 }; 555 556 /** 557 * Dialog that allows editing of volume nickname. 558 */ 559 public static class RenameFragment extends DialogFragment { 560 public static void show(PrivateVolumeSettings parent, VolumeInfo vol) { 561 if (!parent.isAdded()) return; 562 563 final RenameFragment dialog = new RenameFragment(); 564 dialog.setTargetFragment(parent, 0); 565 final Bundle args = new Bundle(); 566 args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid()); 567 dialog.setArguments(args); 568 dialog.show(parent.getFragmentManager(), TAG_RENAME); 569 } 570 571 @Override 572 public Dialog onCreateDialog(Bundle savedInstanceState) { 573 final Context context = getActivity(); 574 final StorageManager storageManager = context.getSystemService(StorageManager.class); 575 576 final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID); 577 final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid); 578 final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid); 579 580 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 581 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 582 583 final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false); 584 final EditText nickname = (EditText) view.findViewById(R.id.edittext); 585 nickname.setText(rec.getNickname()); 586 587 builder.setTitle(R.string.storage_rename_title); 588 builder.setView(view); 589 590 builder.setPositiveButton(R.string.save, 591 new DialogInterface.OnClickListener() { 592 @Override 593 public void onClick(DialogInterface dialog, int which) { 594 // TODO: move to background thread 595 storageManager.setVolumeNickname(fsUuid, 596 nickname.getText().toString()); 597 } 598 }); 599 builder.setNegativeButton(R.string.cancel, null); 600 601 return builder.create(); 602 } 603 } 604 605 public static class OtherInfoFragment extends DialogFragment { 606 public static void show(Fragment parent, String title, VolumeInfo sharedVol) { 607 if (!parent.isAdded()) return; 608 609 final OtherInfoFragment dialog = new OtherInfoFragment(); 610 dialog.setTargetFragment(parent, 0); 611 final Bundle args = new Bundle(); 612 args.putString(Intent.EXTRA_TITLE, title); 613 args.putParcelable(Intent.EXTRA_INTENT, sharedVol.buildBrowseIntent()); 614 dialog.setArguments(args); 615 dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO); 616 } 617 618 @Override 619 public Dialog onCreateDialog(Bundle savedInstanceState) { 620 final Context context = getActivity(); 621 622 final String title = getArguments().getString(Intent.EXTRA_TITLE); 623 final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT); 624 625 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 626 builder.setMessage( 627 TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title)); 628 629 builder.setPositiveButton(R.string.storage_menu_explore, 630 new DialogInterface.OnClickListener() { 631 @Override 632 public void onClick(DialogInterface dialog, int which) { 633 startActivity(intent); 634 } 635 }); 636 builder.setNegativeButton(android.R.string.cancel, null); 637 638 return builder.create(); 639 } 640 } 641 642 public static class UserInfoFragment extends DialogFragment { 643 public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) { 644 if (!parent.isAdded()) return; 645 646 final UserInfoFragment dialog = new UserInfoFragment(); 647 dialog.setTargetFragment(parent, 0); 648 final Bundle args = new Bundle(); 649 args.putCharSequence(Intent.EXTRA_TITLE, userLabel); 650 args.putCharSequence(Intent.EXTRA_SUBJECT, userSize); 651 dialog.setArguments(args); 652 dialog.show(parent.getFragmentManager(), TAG_USER_INFO); 653 } 654 655 @Override 656 public Dialog onCreateDialog(Bundle savedInstanceState) { 657 final Context context = getActivity(); 658 659 final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE); 660 final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT); 661 662 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 663 builder.setMessage(TextUtils.expandTemplate( 664 getText(R.string.storage_detail_dialog_user), userLabel, userSize)); 665 666 builder.setPositiveButton(android.R.string.ok, null); 667 668 return builder.create(); 669 } 670 } 671 672 /** 673 * Dialog to request user confirmation before clearing all cache data. 674 */ 675 public static class ConfirmClearCacheFragment extends DialogFragment { 676 public static void show(Fragment parent) { 677 if (!parent.isAdded()) return; 678 679 final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment(); 680 dialog.setTargetFragment(parent, 0); 681 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE); 682 } 683 684 @Override 685 public Dialog onCreateDialog(Bundle savedInstanceState) { 686 final Context context = getActivity(); 687 688 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 689 builder.setTitle(R.string.memory_clear_cache_title); 690 builder.setMessage(getString(R.string.memory_clear_cache_message)); 691 692 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 693 @Override 694 public void onClick(DialogInterface dialog, int which) { 695 final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment(); 696 final PackageManager pm = context.getPackageManager(); 697 final List<PackageInfo> infos = pm.getInstalledPackages(0); 698 final ClearCacheObserver observer = new ClearCacheObserver( 699 target, infos.size()); 700 for (PackageInfo info : infos) { 701 pm.deleteApplicationCacheFiles(info.packageName, observer); 702 } 703 } 704 }); 705 builder.setNegativeButton(android.R.string.cancel, null); 706 707 return builder.create(); 708 } 709 } 710 711 private static class ClearCacheObserver extends IPackageDataObserver.Stub { 712 private final PrivateVolumeSettings mTarget; 713 private int mRemaining; 714 715 public ClearCacheObserver(PrivateVolumeSettings target, int remaining) { 716 mTarget = target; 717 mRemaining = remaining; 718 } 719 720 @Override 721 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 722 synchronized (this) { 723 if (--mRemaining == 0) { 724 mTarget.getActivity().runOnUiThread(new Runnable() { 725 @Override 726 public void run() { 727 mTarget.update(); 728 } 729 }); 730 } 731 } 732 } 733 } 734 } 735