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