AppStorageSettings.java revision ad7aa871e78d36bbd267f3dff47ccc3d1caa6031
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 17package com.android.settings.applications; 18 19import android.app.ActivityManager; 20import android.app.AlertDialog; 21import android.app.AppGlobals; 22import android.app.LoaderManager; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.content.Loader; 27import android.content.UriPermission; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.IPackageDataObserver; 30import android.content.pm.PackageManager; 31import android.content.pm.ProviderInfo; 32import android.os.Bundle; 33import android.os.Environment; 34import android.os.Handler; 35import android.os.Message; 36import android.os.RemoteException; 37import android.os.UserHandle; 38import android.os.storage.StorageManager; 39import android.os.storage.VolumeInfo; 40import android.support.v7.preference.Preference; 41import android.support.v7.preference.PreferenceCategory; 42import android.text.format.Formatter; 43import android.util.Log; 44import android.util.MutableInt; 45import android.view.View; 46import android.view.View.OnClickListener; 47import android.widget.Button; 48 49import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 50import com.android.settings.R; 51import com.android.settings.Utils; 52import com.android.settings.deviceinfo.StorageWizardMoveConfirm; 53import com.android.settingslib.RestrictedLockUtils; 54import com.android.settingslib.applications.ApplicationsState; 55import com.android.settingslib.applications.ApplicationsState.AppEntry; 56import com.android.settingslib.applications.ApplicationsState.Callbacks; 57import com.android.settingslib.applications.StorageStatsSource; 58import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; 59 60import java.util.Collections; 61import java.util.List; 62import java.util.Map; 63import java.util.Objects; 64import java.util.TreeMap; 65 66import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; 67import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; 68 69public 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_EXTERNAL_CODE_SIZE = "external_code_size"; 98 private static final String KEY_DATA_SIZE = "data_size"; 99 private static final String KEY_EXTERNAL_DATA_SIZE = "external_data_size"; 100 private static final String KEY_CACHE_SIZE = "cache_size"; 101 102 private static final String KEY_CLEAR_DATA = "clear_data_button"; 103 private static final String KEY_CLEAR_CACHE = "clear_cache_button"; 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 Preference mTotalSize; 109 private Preference mAppSize; 110 private Preference mDataSize; 111 private Preference mExternalCodeSize; 112 private Preference mExternalDataSize; 113 114 // Views related to cache info 115 private Preference mCacheSize; 116 private Button mClearDataButton; 117 private Button mClearCacheButton; 118 119 private Preference mStorageUsed; 120 private Button mChangeStorageButton; 121 122 // Views related to URI permissions 123 private Button mClearUriButton; 124 private LayoutPreference mClearUri; 125 private PreferenceCategory mUri; 126 127 private boolean mCanClearData = true; 128 private boolean mHaveSizes = false; 129 130 private AppStorageStats mLastResult; 131 private long mLastCodeSize = -1; 132 private long mLastDataSize = -1; 133 private long mLastExternalCodeSize = -1; 134 private long mLastExternalDataSize = -1; 135 private long mLastCacheSize = -1; 136 private long mLastTotalSize = -1; 137 138 private ClearCacheObserver mClearCacheObserver; 139 private ClearUserDataObserver mClearDataObserver; 140 141 // Resource strings 142 private CharSequence mInvalidSizeStr; 143 private CharSequence mComputingStr; 144 145 private VolumeInfo[] mCandidates; 146 private AlertDialog.Builder mDialogBuilder; 147 private ApplicationInfo mInfo; 148 149 @Override 150 public void onCreate(Bundle savedInstanceState) { 151 super.onCreate(savedInstanceState); 152 153 addPreferencesFromResource(R.xml.app_storage_settings); 154 setupViews(); 155 initMoveDialog(); 156 } 157 158 @Override 159 public void onResume() { 160 super.onResume(); 161 updateSize(); 162 } 163 164 private void setupViews() { 165 mComputingStr = getActivity().getText(R.string.computing_size); 166 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 167 168 // Set default values on sizes 169 mTotalSize = findPreference(KEY_TOTAL_SIZE); 170 mAppSize = findPreference(KEY_APP_SIZE); 171 mDataSize = findPreference(KEY_DATA_SIZE); 172 mExternalCodeSize = findPreference(KEY_EXTERNAL_CODE_SIZE); 173 mExternalDataSize = findPreference(KEY_EXTERNAL_DATA_SIZE); 174 175 if (Environment.isExternalStorageEmulated()) { 176 PreferenceCategory category = (PreferenceCategory) findPreference(KEY_STORAGE_CATEGORY); 177 category.removePreference(mExternalCodeSize); 178 category.removePreference(mExternalDataSize); 179 } 180 mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_DATA)) 181 .findViewById(R.id.button); 182 183 mStorageUsed = findPreference(KEY_STORAGE_USED); 184 mChangeStorageButton = (Button) ((LayoutPreference) findPreference(KEY_CHANGE_STORAGE)) 185 .findViewById(R.id.button); 186 mChangeStorageButton.setText(R.string.change); 187 mChangeStorageButton.setOnClickListener(this); 188 189 // Cache section 190 mCacheSize = findPreference(KEY_CACHE_SIZE); 191 mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_CACHE)) 192 .findViewById(R.id.button); 193 mClearCacheButton.setText(R.string.clear_cache_btn_text); 194 195 // URI permissions section 196 mUri = (PreferenceCategory) findPreference(KEY_URI_CATEGORY); 197 mClearUri = (LayoutPreference) mUri.findPreference(KEY_CLEAR_URI); 198 mClearUriButton = (Button) mClearUri.findViewById(R.id.button); 199 mClearUriButton.setText(R.string.clear_uri_btn_text); 200 mClearUriButton.setOnClickListener(this); 201 } 202 203 @Override 204 public void onClick(View v) { 205 if (v == mClearCacheButton) { 206 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 207 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 208 getActivity(), mAppsControlDisallowedAdmin); 209 return; 210 } else if (mClearCacheObserver == null) { // Lazy initialization of observer 211 mClearCacheObserver = new ClearCacheObserver(); 212 } 213 mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 214 } else if (v == mClearDataButton) { 215 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 216 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 217 getActivity(), mAppsControlDisallowedAdmin); 218 } else if (mAppEntry.info.manageSpaceActivityName != null) { 219 if (!Utils.isMonkeyRunning()) { 220 Intent intent = new Intent(Intent.ACTION_DEFAULT); 221 intent.setClassName(mAppEntry.info.packageName, 222 mAppEntry.info.manageSpaceActivityName); 223 startActivityForResult(intent, REQUEST_MANAGE_SPACE); 224 } 225 } else { 226 showDialogInner(DLG_CLEAR_DATA, 0); 227 } 228 } else if (v == mChangeStorageButton && mDialogBuilder != null && !isMoveInProgress()) { 229 mDialogBuilder.show(); 230 } else if (v == mClearUriButton) { 231 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 232 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 233 getActivity(), mAppsControlDisallowedAdmin); 234 } else { 235 clearUriPermissions(); 236 } 237 } 238 } 239 240 private boolean isMoveInProgress() { 241 try { 242 // TODO: define a cleaner API for this 243 AppGlobals.getPackageManager().checkPackageStartable(mPackageName, 244 UserHandle.myUserId()); 245 return false; 246 } catch (RemoteException | SecurityException e) { 247 return true; 248 } 249 } 250 251 @Override 252 public void onClick(DialogInterface dialog, int which) { 253 final Context context = getActivity(); 254 255 // If not current volume, kick off move wizard 256 final VolumeInfo targetVol = mCandidates[which]; 257 final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume( 258 mAppEntry.info); 259 if (!Objects.equals(targetVol, currentVol)) { 260 final Intent intent = new Intent(context, StorageWizardMoveConfirm.class); 261 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId()); 262 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 263 startActivity(intent); 264 } 265 dialog.dismiss(); 266 } 267 268 private String getSizeStr(long size) { 269 if (size == SIZE_INVALID) { 270 return mInvalidSizeStr.toString(); 271 } 272 return Formatter.formatFileSize(getActivity(), size); 273 } 274 275 @Override 276 protected boolean refreshUi() { 277 retrieveAppEntry(); 278 if (mAppEntry == null) { 279 return false; 280 } 281 updateUiWithSize(mLastResult); 282 refreshGrantedUriPermissions(); 283 284 final VolumeInfo currentVol = getActivity().getPackageManager() 285 .getPackageCurrentVolume(mAppEntry.info); 286 final StorageManager storage = getContext().getSystemService(StorageManager.class); 287 mStorageUsed.setSummary(storage.getBestVolumeDescription(currentVol)); 288 289 refreshButtons(); 290 291 return true; 292 } 293 294 private void refreshButtons() { 295 initMoveDialog(); 296 initDataButtons(); 297 } 298 299 private void initDataButtons() { 300 final boolean appHasSpaceManagementUI = mAppEntry.info.manageSpaceActivityName != null; 301 final boolean appHasActiveAdmins = mDpm.packageHasActiveAdmins(mPackageName); 302 // Check that SYSTEM_APP flag is set, and ALLOW_CLEAR_USER_DATA is not set. 303 final boolean isNonClearableSystemApp = 304 (mAppEntry.info.flags & (FLAG_SYSTEM | FLAG_ALLOW_CLEAR_USER_DATA)) == FLAG_SYSTEM; 305 final boolean appRestrictsClearingData = isNonClearableSystemApp || appHasActiveAdmins; 306 307 final Intent intent = new Intent(Intent.ACTION_DEFAULT); 308 if (appHasSpaceManagementUI) { 309 intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName); 310 } 311 final boolean isManageSpaceActivityAvailable = 312 getPackageManager().resolveActivity(intent, 0) != null; 313 314 if ((!appHasSpaceManagementUI && appRestrictsClearingData) 315 || !isManageSpaceActivityAvailable) { 316 mClearDataButton.setText(R.string.clear_user_data_text); 317 mClearDataButton.setEnabled(false); 318 mCanClearData = false; 319 } else { 320 if (appHasSpaceManagementUI) { 321 mClearDataButton.setText(R.string.manage_space_text); 322 } else { 323 mClearDataButton.setText(R.string.clear_user_data_text); 324 } 325 mClearDataButton.setOnClickListener(this); 326 } 327 328 if (mAppsControlDisallowedBySystem) { 329 mClearDataButton.setEnabled(false); 330 } 331 } 332 333 private void initMoveDialog() { 334 final Context context = getActivity(); 335 final StorageManager storage = context.getSystemService(StorageManager.class); 336 337 final List<VolumeInfo> candidates = context.getPackageManager() 338 .getPackageCandidateVolumes(mAppEntry.info); 339 if (candidates.size() > 1) { 340 Collections.sort(candidates, VolumeInfo.getDescriptionComparator()); 341 342 CharSequence[] labels = new CharSequence[candidates.size()]; 343 int current = -1; 344 for (int i = 0; i < candidates.size(); i++) { 345 final String volDescrip = storage.getBestVolumeDescription(candidates.get(i)); 346 if (Objects.equals(volDescrip, mStorageUsed.getSummary())) { 347 current = i; 348 } 349 labels[i] = volDescrip; 350 } 351 mCandidates = candidates.toArray(new VolumeInfo[candidates.size()]); 352 mDialogBuilder = new AlertDialog.Builder(getContext()) 353 .setTitle(R.string.change_storage) 354 .setSingleChoiceItems(labels, current, this) 355 .setNegativeButton(R.string.cancel, null); 356 } else { 357 removePreference(KEY_STORAGE_USED); 358 removePreference(KEY_CHANGE_STORAGE); 359 removePreference(KEY_STORAGE_SPACE); 360 } 361 } 362 363 /* 364 * Private method to initiate clearing user data when the user clicks the clear data 365 * button for a system package 366 */ 367 private void initiateClearUserData() { 368 mClearDataButton.setEnabled(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, "Couldnt clear application user data for package:"+packageName); 381 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 382 } else { 383 mClearDataButton.setText(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 mClearDataButton.setText(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 mClearDataButton.setEnabled(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<UriPermission> 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 (UriPermission perm : perms) { 423 String authority = perm.getUri().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 // Synchronously revoke the permissions. 461 final ActivityManager am = (ActivityManager) getActivity().getSystemService( 462 Context.ACTIVITY_SERVICE); 463 am.clearGrantedUriPermissions(mAppEntry.info.packageName); 464 465 // Update UI 466 refreshGrantedUriPermissions(); 467 } 468 469 private void removeUriPermissionsFromUi() { 470 // Remove all preferences but the clear button. 471 int count = mUri.getPreferenceCount(); 472 for (int i = count - 1; i >= 0; i--) { 473 Preference pref = mUri.getPreference(i); 474 if (pref != mClearUri) { 475 mUri.removePreference(pref); 476 } 477 } 478 } 479 480 @Override 481 protected AlertDialog createDialog(int id, int errorCode) { 482 switch (id) { 483 case DLG_CLEAR_DATA: 484 return new AlertDialog.Builder(getActivity()) 485 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 486 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 487 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 488 public void onClick(DialogInterface dialog, int which) { 489 // Clear user data here 490 initiateClearUserData(); 491 } 492 }) 493 .setNegativeButton(R.string.dlg_cancel, null) 494 .create(); 495 case DLG_CANNOT_CLEAR_DATA: 496 return new AlertDialog.Builder(getActivity()) 497 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 498 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 499 .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 500 public void onClick(DialogInterface dialog, int which) { 501 mClearDataButton.setEnabled(false); 502 //force to recompute changed value 503 setIntentAndFinish(false, false); 504 } 505 }) 506 .create(); 507 } 508 return null; 509 } 510 511 @Override 512 public void onPackageSizeChanged(String packageName) { 513 } 514 515 @Override 516 public Loader<AppStorageStats> onCreateLoader(int id, Bundle args) { 517 Context context = getContext(); 518 return new FetchPackageStorageAsyncLoader( 519 context, new StorageStatsSource(context), mInfo, UserHandle.of(mUserId)); 520 } 521 522 @Override 523 public void onLoadFinished(Loader<AppStorageStats> loader, AppStorageStats result) { 524 mLastResult = result; 525 updateUiWithSize(result); 526 } 527 528 @Override 529 public void onLoaderReset(Loader<AppStorageStats> loader) { 530 } 531 532 private void updateSize() { 533 PackageManager packageManager = getPackageManager(); 534 try { 535 mInfo = packageManager.getApplicationInfo(mPackageName, 0); 536 } catch (PackageManager.NameNotFoundException e) { 537 Log.e(TAG, "Could not find package", e); 538 } 539 540 if (mInfo == null) { 541 return; 542 } 543 544 getLoaderManager().restartLoader(1, Bundle.EMPTY, this); 545 } 546 547 private void updateUiWithSize(AppStorageStats result) { 548 if (result == null) { 549 mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; 550 if (!mHaveSizes) { 551 mAppSize.setSummary(mComputingStr); 552 mDataSize.setSummary(mComputingStr); 553 mCacheSize.setSummary(mComputingStr); 554 mTotalSize.setSummary(mComputingStr); 555 } 556 mClearDataButton.setEnabled(false); 557 mClearCacheButton.setEnabled(false); 558 } else { 559 mHaveSizes = true; 560 long codeSize = result.getCodeBytes(); 561 long dataSize = result.getDataBytes(); 562 if (mLastCodeSize != codeSize) { 563 mLastCodeSize = codeSize; 564 mAppSize.setSummary(getSizeStr(codeSize)); 565 } 566 if (mLastDataSize != dataSize) { 567 mLastDataSize = dataSize; 568 mDataSize.setSummary(getSizeStr(dataSize)); 569 } 570 long cacheSize = result.getCacheBytes(); 571 if (mLastCacheSize != cacheSize) { 572 mLastCacheSize = cacheSize; 573 mCacheSize.setSummary(getSizeStr(cacheSize)); 574 } 575 576 long totalSize = codeSize + dataSize + cacheSize; 577 if (mLastTotalSize != totalSize) { 578 mLastTotalSize = totalSize; 579 mTotalSize.setSummary(getSizeStr(totalSize)); 580 } 581 582 if (dataSize <= 0 || !mCanClearData) { 583 mClearDataButton.setEnabled(false); 584 } else { 585 mClearDataButton.setEnabled(true); 586 mClearDataButton.setOnClickListener(this); 587 } 588 if (cacheSize <= 0) { 589 mClearCacheButton.setEnabled(false); 590 } else { 591 mClearCacheButton.setEnabled(true); 592 mClearCacheButton.setOnClickListener(this); 593 } 594 } 595 if (mAppsControlDisallowedBySystem) { 596 mClearCacheButton.setEnabled(false); 597 mClearDataButton.setEnabled(false); 598 } 599 } 600 601 private final Handler mHandler = new Handler() { 602 public void handleMessage(Message msg) { 603 if (getView() == null) { 604 return; 605 } 606 switch (msg.what) { 607 case MSG_CLEAR_USER_DATA: 608 processClearMsg(msg); 609 break; 610 case MSG_CLEAR_CACHE: 611 // Refresh size info 612 updateSize(); 613 break; 614 } 615 } 616 }; 617 618 @Override 619 public int getMetricsCategory() { 620 return MetricsEvent.APPLICATIONS_APP_STORAGE; 621 } 622 623 class ClearCacheObserver extends IPackageDataObserver.Stub { 624 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 625 final Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); 626 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 627 mHandler.sendMessage(msg); 628 } 629 } 630 631 class ClearUserDataObserver extends IPackageDataObserver.Stub { 632 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 633 final Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); 634 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 635 mHandler.sendMessage(msg); 636 } 637 } 638} 639