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