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