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