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 17package com.android.settings.applications; 18 19import android.app.ActivityManager; 20import android.app.AlertDialog; 21import android.app.AppGlobals; 22import android.content.Context; 23import android.content.DialogInterface; 24import android.content.Intent; 25import android.content.UriPermission; 26import android.content.pm.ApplicationInfo; 27import android.content.pm.IPackageDataObserver; 28import android.content.pm.PackageManager; 29import android.content.pm.ProviderInfo; 30import android.os.Bundle; 31import android.os.Environment; 32import android.os.Handler; 33import android.os.Message; 34import android.os.RemoteException; 35import android.os.UserHandle; 36import android.os.storage.StorageManager; 37import android.os.storage.VolumeInfo; 38import android.support.v7.preference.Preference; 39import android.support.v7.preference.PreferenceCategory; 40import android.text.format.Formatter; 41import android.util.Log; 42import android.util.MutableInt; 43import android.view.View; 44import android.view.View.OnClickListener; 45import android.widget.Button; 46 47import com.android.internal.logging.MetricsProto.MetricsEvent; 48import com.android.settings.R; 49import com.android.settings.Utils; 50import com.android.settings.deviceinfo.StorageWizardMoveConfirm; 51import com.android.settingslib.RestrictedLockUtils; 52import com.android.settingslib.applications.ApplicationsState; 53import com.android.settingslib.applications.ApplicationsState.AppEntry; 54import com.android.settingslib.applications.ApplicationsState.Callbacks; 55 56import java.util.ArrayList; 57import java.util.Collections; 58import java.util.List; 59import java.util.Map; 60import java.util.Objects; 61import java.util.TreeMap; 62 63import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; 64import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; 65 66public class AppStorageSettings extends AppInfoWithHeader 67 implements OnClickListener, Callbacks, DialogInterface.OnClickListener { 68 private static final String TAG = AppStorageSettings.class.getSimpleName(); 69 70 //internal constants used in Handler 71 private static final int OP_SUCCESSFUL = 1; 72 private static final int OP_FAILED = 2; 73 private static final int MSG_CLEAR_USER_DATA = 1; 74 private static final int MSG_CLEAR_CACHE = 3; 75 76 // invalid size value used initially and also when size retrieval through PackageManager 77 // fails for whatever reason 78 private static final int SIZE_INVALID = -1; 79 80 // Result code identifiers 81 public static final int REQUEST_MANAGE_SPACE = 2; 82 83 private static final int DLG_CLEAR_DATA = DLG_BASE + 1; 84 private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2; 85 86 private static final String KEY_STORAGE_USED = "storage_used"; 87 private static final String KEY_CHANGE_STORAGE = "change_storage_button"; 88 private static final String KEY_STORAGE_SPACE = "storage_space"; 89 private static final String KEY_STORAGE_CATEGORY = "storage_category"; 90 91 private static final String KEY_TOTAL_SIZE = "total_size"; 92 private static final String KEY_APP_SIZE = "app_size"; 93 private static final String KEY_EXTERNAL_CODE_SIZE = "external_code_size"; 94 private static final String KEY_DATA_SIZE = "data_size"; 95 private static final String KEY_EXTERNAL_DATA_SIZE = "external_data_size"; 96 private static final String KEY_CACHE_SIZE = "cache_size"; 97 98 private static final String KEY_CLEAR_DATA = "clear_data_button"; 99 private static final String KEY_CLEAR_CACHE = "clear_cache_button"; 100 101 private static final String KEY_URI_CATEGORY = "uri_category"; 102 private static final String KEY_CLEAR_URI = "clear_uri_button"; 103 104 private Preference mTotalSize; 105 private Preference mAppSize; 106 private Preference mDataSize; 107 private Preference mExternalCodeSize; 108 private Preference mExternalDataSize; 109 110 // Views related to cache info 111 private Preference mCacheSize; 112 private Button mClearDataButton; 113 private Button mClearCacheButton; 114 115 private Preference mStorageUsed; 116 private Button mChangeStorageButton; 117 118 // Views related to URI permissions 119 private Button mClearUriButton; 120 private LayoutPreference mClearUri; 121 private PreferenceCategory mUri; 122 123 private boolean mCanClearData = true; 124 private boolean mHaveSizes = false; 125 126 private long mLastCodeSize = -1; 127 private long mLastDataSize = -1; 128 private long mLastExternalCodeSize = -1; 129 private long mLastExternalDataSize = -1; 130 private long mLastCacheSize = -1; 131 private long mLastTotalSize = -1; 132 133 private ClearCacheObserver mClearCacheObserver; 134 private ClearUserDataObserver mClearDataObserver; 135 136 // Resource strings 137 private CharSequence mInvalidSizeStr; 138 private CharSequence mComputingStr; 139 140 private VolumeInfo[] mCandidates; 141 private AlertDialog.Builder mDialogBuilder; 142 143 @Override 144 public void onCreate(Bundle savedInstanceState) { 145 super.onCreate(savedInstanceState); 146 147 addPreferencesFromResource(R.xml.app_storage_settings); 148 setupViews(); 149 initMoveDialog(); 150 } 151 152 @Override 153 public void onResume() { 154 super.onResume(); 155 mState.requestSize(mPackageName, mUserId); 156 } 157 158 private void setupViews() { 159 mComputingStr = getActivity().getText(R.string.computing_size); 160 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 161 162 // Set default values on sizes 163 mTotalSize = findPreference(KEY_TOTAL_SIZE); 164 mAppSize = findPreference(KEY_APP_SIZE); 165 mDataSize = findPreference(KEY_DATA_SIZE); 166 mExternalCodeSize = findPreference(KEY_EXTERNAL_CODE_SIZE); 167 mExternalDataSize = findPreference(KEY_EXTERNAL_DATA_SIZE); 168 169 if (Environment.isExternalStorageEmulated()) { 170 PreferenceCategory category = (PreferenceCategory) findPreference(KEY_STORAGE_CATEGORY); 171 category.removePreference(mExternalCodeSize); 172 category.removePreference(mExternalDataSize); 173 } 174 mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_DATA)) 175 .findViewById(R.id.button); 176 177 mStorageUsed = findPreference(KEY_STORAGE_USED); 178 mChangeStorageButton = (Button) ((LayoutPreference) findPreference(KEY_CHANGE_STORAGE)) 179 .findViewById(R.id.button); 180 mChangeStorageButton.setText(R.string.change); 181 mChangeStorageButton.setOnClickListener(this); 182 183 // Cache section 184 mCacheSize = findPreference(KEY_CACHE_SIZE); 185 mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_CACHE)) 186 .findViewById(R.id.button); 187 mClearCacheButton.setText(R.string.clear_cache_btn_text); 188 189 // URI permissions section 190 mUri = (PreferenceCategory) findPreference(KEY_URI_CATEGORY); 191 mClearUri = (LayoutPreference) mUri.findPreference(KEY_CLEAR_URI); 192 mClearUriButton = (Button) mClearUri.findViewById(R.id.button); 193 mClearUriButton.setText(R.string.clear_uri_btn_text); 194 mClearUriButton.setOnClickListener(this); 195 } 196 197 @Override 198 public void onClick(View v) { 199 if (v == mClearCacheButton) { 200 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 201 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 202 getActivity(), mAppsControlDisallowedAdmin); 203 return; 204 } else if (mClearCacheObserver == null) { // Lazy initialization of observer 205 mClearCacheObserver = new ClearCacheObserver(); 206 } 207 mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 208 } else if (v == mClearDataButton) { 209 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 210 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 211 getActivity(), mAppsControlDisallowedAdmin); 212 } else if (mAppEntry.info.manageSpaceActivityName != null) { 213 if (!Utils.isMonkeyRunning()) { 214 Intent intent = new Intent(Intent.ACTION_DEFAULT); 215 intent.setClassName(mAppEntry.info.packageName, 216 mAppEntry.info.manageSpaceActivityName); 217 startActivityForResult(intent, REQUEST_MANAGE_SPACE); 218 } 219 } else { 220 showDialogInner(DLG_CLEAR_DATA, 0); 221 } 222 } else if (v == mChangeStorageButton && mDialogBuilder != null && !isMoveInProgress()) { 223 mDialogBuilder.show(); 224 } else if (v == mClearUriButton) { 225 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 226 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 227 getActivity(), mAppsControlDisallowedAdmin); 228 } else { 229 clearUriPermissions(); 230 } 231 } 232 } 233 234 private boolean isMoveInProgress() { 235 try { 236 // TODO: define a cleaner API for this 237 AppGlobals.getPackageManager().checkPackageStartable(mPackageName, 238 UserHandle.myUserId()); 239 return false; 240 } catch (RemoteException | SecurityException e) { 241 return true; 242 } 243 } 244 245 @Override 246 public void onClick(DialogInterface dialog, int which) { 247 final Context context = getActivity(); 248 249 // If not current volume, kick off move wizard 250 final VolumeInfo targetVol = mCandidates[which]; 251 final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume( 252 mAppEntry.info); 253 if (!Objects.equals(targetVol, currentVol)) { 254 final Intent intent = new Intent(context, StorageWizardMoveConfirm.class); 255 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId()); 256 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 257 startActivity(intent); 258 } 259 dialog.dismiss(); 260 } 261 262 private String getSizeStr(long size) { 263 if (size == SIZE_INVALID) { 264 return mInvalidSizeStr.toString(); 265 } 266 return Formatter.formatFileSize(getActivity(), size); 267 } 268 269 private void refreshSizeInfo() { 270 if (mAppEntry.size == ApplicationsState.SIZE_INVALID 271 || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) { 272 mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; 273 if (!mHaveSizes) { 274 mAppSize.setSummary(mComputingStr); 275 mDataSize.setSummary(mComputingStr); 276 mCacheSize.setSummary(mComputingStr); 277 mTotalSize.setSummary(mComputingStr); 278 } 279 mClearDataButton.setEnabled(false); 280 mClearCacheButton.setEnabled(false); 281 } else { 282 mHaveSizes = true; 283 long codeSize = mAppEntry.codeSize; 284 long dataSize = mAppEntry.dataSize; 285 if (Environment.isExternalStorageEmulated()) { 286 codeSize += mAppEntry.externalCodeSize; 287 dataSize += mAppEntry.externalDataSize; 288 } else { 289 if (mLastExternalCodeSize != mAppEntry.externalCodeSize) { 290 mLastExternalCodeSize = mAppEntry.externalCodeSize; 291 mExternalCodeSize.setSummary(getSizeStr(mAppEntry.externalCodeSize)); 292 } 293 if (mLastExternalDataSize != mAppEntry.externalDataSize) { 294 mLastExternalDataSize = mAppEntry.externalDataSize; 295 mExternalDataSize.setSummary(getSizeStr( mAppEntry.externalDataSize)); 296 } 297 } 298 if (mLastCodeSize != codeSize) { 299 mLastCodeSize = codeSize; 300 mAppSize.setSummary(getSizeStr(codeSize)); 301 } 302 if (mLastDataSize != dataSize) { 303 mLastDataSize = dataSize; 304 mDataSize.setSummary(getSizeStr(dataSize)); 305 } 306 long cacheSize = mAppEntry.cacheSize + mAppEntry.externalCacheSize; 307 if (mLastCacheSize != cacheSize) { 308 mLastCacheSize = cacheSize; 309 mCacheSize.setSummary(getSizeStr(cacheSize)); 310 } 311 if (mLastTotalSize != mAppEntry.size) { 312 mLastTotalSize = mAppEntry.size; 313 mTotalSize.setSummary(getSizeStr(mAppEntry.size)); 314 } 315 316 if ((mAppEntry.dataSize+ mAppEntry.externalDataSize) <= 0 || !mCanClearData) { 317 mClearDataButton.setEnabled(false); 318 } else { 319 mClearDataButton.setEnabled(true); 320 mClearDataButton.setOnClickListener(this); 321 } 322 if (cacheSize <= 0) { 323 mClearCacheButton.setEnabled(false); 324 } else { 325 mClearCacheButton.setEnabled(true); 326 mClearCacheButton.setOnClickListener(this); 327 } 328 } 329 if (mAppsControlDisallowedBySystem) { 330 mClearCacheButton.setEnabled(false); 331 mClearDataButton.setEnabled(false); 332 } 333 } 334 335 @Override 336 protected boolean refreshUi() { 337 retrieveAppEntry(); 338 if (mAppEntry == null) { 339 return false; 340 } 341 refreshSizeInfo(); 342 refreshGrantedUriPermissions(); 343 344 final VolumeInfo currentVol = getActivity().getPackageManager() 345 .getPackageCurrentVolume(mAppEntry.info); 346 final StorageManager storage = getContext().getSystemService(StorageManager.class); 347 mStorageUsed.setSummary(storage.getBestVolumeDescription(currentVol)); 348 349 refreshButtons(); 350 351 return true; 352 } 353 354 private void refreshButtons() { 355 initMoveDialog(); 356 initDataButtons(); 357 } 358 359 private void initDataButtons() { 360 final boolean appHasSpaceManagementUI = mAppEntry.info.manageSpaceActivityName != null; 361 final boolean appHasActiveAdmins = mDpm.packageHasActiveAdmins(mPackageName); 362 // Check that SYSTEM_APP flag is set, and ALLOW_CLEAR_USER_DATA is not set. 363 final boolean isNonClearableSystemApp = 364 (mAppEntry.info.flags & (FLAG_SYSTEM | FLAG_ALLOW_CLEAR_USER_DATA)) == FLAG_SYSTEM; 365 final boolean appRestrictsClearingData = isNonClearableSystemApp || appHasActiveAdmins; 366 367 final Intent intent = new Intent(Intent.ACTION_DEFAULT); 368 if (appHasSpaceManagementUI) { 369 intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName); 370 } 371 final boolean isManageSpaceActivityAvailable = 372 getPackageManager().resolveActivity(intent, 0) != null; 373 374 if ((!appHasSpaceManagementUI && appRestrictsClearingData) 375 || !isManageSpaceActivityAvailable) { 376 mClearDataButton.setText(R.string.clear_user_data_text); 377 mClearDataButton.setEnabled(false); 378 mCanClearData = false; 379 } else { 380 if (appHasSpaceManagementUI) { 381 mClearDataButton.setText(R.string.manage_space_text); 382 } else { 383 mClearDataButton.setText(R.string.clear_user_data_text); 384 } 385 mClearDataButton.setOnClickListener(this); 386 } 387 388 if (mAppsControlDisallowedBySystem) { 389 mClearDataButton.setEnabled(false); 390 } 391 } 392 393 private void initMoveDialog() { 394 final Context context = getActivity(); 395 final StorageManager storage = context.getSystemService(StorageManager.class); 396 397 final List<VolumeInfo> candidates = context.getPackageManager() 398 .getPackageCandidateVolumes(mAppEntry.info); 399 if (candidates.size() > 1) { 400 Collections.sort(candidates, VolumeInfo.getDescriptionComparator()); 401 402 CharSequence[] labels = new CharSequence[candidates.size()]; 403 int current = -1; 404 for (int i = 0; i < candidates.size(); i++) { 405 final String volDescrip = storage.getBestVolumeDescription(candidates.get(i)); 406 if (Objects.equals(volDescrip, mStorageUsed.getSummary())) { 407 current = i; 408 } 409 labels[i] = volDescrip; 410 } 411 mCandidates = candidates.toArray(new VolumeInfo[candidates.size()]); 412 mDialogBuilder = new AlertDialog.Builder(getContext()) 413 .setTitle(R.string.change_storage) 414 .setSingleChoiceItems(labels, current, this) 415 .setNegativeButton(R.string.cancel, null); 416 } else { 417 removePreference(KEY_STORAGE_USED); 418 removePreference(KEY_CHANGE_STORAGE); 419 removePreference(KEY_STORAGE_SPACE); 420 } 421 } 422 423 /* 424 * Private method to initiate clearing user data when the user clicks the clear data 425 * button for a system package 426 */ 427 private void initiateClearUserData() { 428 mClearDataButton.setEnabled(false); 429 // Invoke uninstall or clear user data based on sysPackage 430 String packageName = mAppEntry.info.packageName; 431 Log.i(TAG, "Clearing user data for package : " + packageName); 432 if (mClearDataObserver == null) { 433 mClearDataObserver = new ClearUserDataObserver(); 434 } 435 ActivityManager am = (ActivityManager) 436 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 437 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 438 if (!res) { 439 // Clearing data failed for some obscure reason. Just log error for now 440 Log.i(TAG, "Couldnt clear application user data for package:"+packageName); 441 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 442 } else { 443 mClearDataButton.setText(R.string.recompute_size); 444 } 445 } 446 447 /* 448 * Private method to handle clear message notification from observer when 449 * the async operation from PackageManager is complete 450 */ 451 private void processClearMsg(Message msg) { 452 int result = msg.arg1; 453 String packageName = mAppEntry.info.packageName; 454 mClearDataButton.setText(R.string.clear_user_data_text); 455 if (result == OP_SUCCESSFUL) { 456 Log.i(TAG, "Cleared user data for package : "+packageName); 457 mState.requestSize(mPackageName, mUserId); 458 } else { 459 mClearDataButton.setEnabled(true); 460 } 461 } 462 463 private void refreshGrantedUriPermissions() { 464 // Clear UI first (in case the activity has been resumed) 465 removeUriPermissionsFromUi(); 466 467 // Gets all URI permissions from am. 468 ActivityManager am = (ActivityManager) getActivity().getSystemService( 469 Context.ACTIVITY_SERVICE); 470 List<UriPermission> perms = 471 am.getGrantedUriPermissions(mAppEntry.info.packageName).getList(); 472 473 if (perms.isEmpty()) { 474 mClearUriButton.setVisibility(View.GONE); 475 return; 476 } 477 478 PackageManager pm = getActivity().getPackageManager(); 479 480 // Group number of URIs by app. 481 Map<CharSequence, MutableInt> uriCounters = new TreeMap<>(); 482 for (UriPermission perm : perms) { 483 String authority = perm.getUri().getAuthority(); 484 ProviderInfo provider = pm.resolveContentProvider(authority, 0); 485 CharSequence app = provider.applicationInfo.loadLabel(pm); 486 MutableInt count = uriCounters.get(app); 487 if (count == null) { 488 uriCounters.put(app, new MutableInt(1)); 489 } else { 490 count.value++; 491 } 492 } 493 494 // Dynamically add the preferences, one per app. 495 int order = 0; 496 for (Map.Entry<CharSequence, MutableInt> entry : uriCounters.entrySet()) { 497 int numberResources = entry.getValue().value; 498 Preference pref = new Preference(getPrefContext()); 499 pref.setTitle(entry.getKey()); 500 pref.setSummary(getPrefContext().getResources() 501 .getQuantityString(R.plurals.uri_permissions_text, numberResources, 502 numberResources)); 503 pref.setSelectable(false); 504 pref.setLayoutResource(R.layout.horizontal_preference); 505 pref.setOrder(order); 506 Log.v(TAG, "Adding preference '" + pref + "' at order " + order); 507 mUri.addPreference(pref); 508 } 509 510 if (mAppsControlDisallowedBySystem) { 511 mClearUriButton.setEnabled(false); 512 } 513 514 mClearUri.setOrder(order); 515 mClearUriButton.setVisibility(View.VISIBLE); 516 517 } 518 519 private void clearUriPermissions() { 520 // Synchronously revoke the permissions. 521 final ActivityManager am = (ActivityManager) getActivity().getSystemService( 522 Context.ACTIVITY_SERVICE); 523 am.clearGrantedUriPermissions(mAppEntry.info.packageName); 524 525 // Update UI 526 refreshGrantedUriPermissions(); 527 } 528 529 private void removeUriPermissionsFromUi() { 530 // Remove all preferences but the clear button. 531 int count = mUri.getPreferenceCount(); 532 for (int i = count - 1; i >= 0; i--) { 533 Preference pref = mUri.getPreference(i); 534 if (pref != mClearUri) { 535 mUri.removePreference(pref); 536 } 537 } 538 } 539 540 @Override 541 protected AlertDialog createDialog(int id, int errorCode) { 542 switch (id) { 543 case DLG_CLEAR_DATA: 544 return new AlertDialog.Builder(getActivity()) 545 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 546 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 547 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 548 public void onClick(DialogInterface dialog, int which) { 549 // Clear user data here 550 initiateClearUserData(); 551 } 552 }) 553 .setNegativeButton(R.string.dlg_cancel, null) 554 .create(); 555 case DLG_CANNOT_CLEAR_DATA: 556 return new AlertDialog.Builder(getActivity()) 557 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 558 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 559 .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 560 public void onClick(DialogInterface dialog, int which) { 561 mClearDataButton.setEnabled(false); 562 //force to recompute changed value 563 setIntentAndFinish(false, false); 564 } 565 }) 566 .create(); 567 } 568 return null; 569 } 570 571 @Override 572 public void onPackageSizeChanged(String packageName) { 573 if (packageName.equals(mAppEntry.info.packageName)) { 574 refreshSizeInfo(); 575 } 576 } 577 578 private final Handler mHandler = new Handler() { 579 public void handleMessage(Message msg) { 580 if (getView() == null) { 581 return; 582 } 583 switch (msg.what) { 584 case MSG_CLEAR_USER_DATA: 585 processClearMsg(msg); 586 break; 587 case MSG_CLEAR_CACHE: 588 // Refresh size info 589 mState.requestSize(mPackageName, mUserId); 590 break; 591 } 592 } 593 }; 594 595 public static CharSequence getSummary(AppEntry appEntry, Context context) { 596 if (appEntry.size == ApplicationsState.SIZE_INVALID 597 || appEntry.size == ApplicationsState.SIZE_UNKNOWN) { 598 return context.getText(R.string.computing_size); 599 } else { 600 CharSequence storageType = context.getString( 601 (appEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 602 ? R.string.storage_type_external 603 : R.string.storage_type_internal); 604 return context.getString(R.string.storage_summary_format, 605 getSize(appEntry, context), storageType); 606 } 607 } 608 609 private static CharSequence getSize(AppEntry appEntry, Context context) { 610 long size = appEntry.size; 611 if (size == SIZE_INVALID) { 612 return context.getText(R.string.invalid_size_value); 613 } 614 return Formatter.formatFileSize(context, size); 615 } 616 617 @Override 618 protected int getMetricsCategory() { 619 return MetricsEvent.APPLICATIONS_APP_STORAGE; 620 } 621 622 class ClearCacheObserver extends IPackageDataObserver.Stub { 623 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 624 final Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); 625 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 626 mHandler.sendMessage(msg); 627 } 628 } 629 630 class ClearUserDataObserver extends IPackageDataObserver.Stub { 631 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 632 final Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); 633 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 634 mHandler.sendMessage(msg); 635 } 636 } 637} 638