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