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