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