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 android.app.ActivityManager; 20 import android.app.AlertDialog; 21 import android.app.AppGlobals; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.UriPermission; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.IPackageDataObserver; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ProviderInfo; 30 import android.os.Bundle; 31 import android.os.Environment; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.os.RemoteException; 35 import android.os.UserHandle; 36 import android.os.storage.StorageManager; 37 import android.os.storage.VolumeInfo; 38 import android.support.v7.preference.Preference; 39 import android.support.v7.preference.PreferenceCategory; 40 import android.text.format.Formatter; 41 import android.util.Log; 42 import android.util.MutableInt; 43 import android.view.View; 44 import android.view.View.OnClickListener; 45 import android.widget.Button; 46 47 import com.android.internal.logging.MetricsProto.MetricsEvent; 48 import com.android.settings.R; 49 import com.android.settings.Utils; 50 import com.android.settings.deviceinfo.StorageWizardMoveConfirm; 51 import com.android.settingslib.RestrictedLockUtils; 52 import com.android.settingslib.applications.ApplicationsState; 53 import com.android.settingslib.applications.ApplicationsState.AppEntry; 54 import com.android.settingslib.applications.ApplicationsState.Callbacks; 55 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.TreeMap; 62 63 public class AppStorageSettings extends AppInfoWithHeader 64 implements OnClickListener, Callbacks, DialogInterface.OnClickListener { 65 private static final String TAG = AppStorageSettings.class.getSimpleName(); 66 67 //internal constants used in Handler 68 private static final int OP_SUCCESSFUL = 1; 69 private static final int OP_FAILED = 2; 70 private static final int MSG_CLEAR_USER_DATA = 1; 71 private static final int MSG_CLEAR_CACHE = 3; 72 73 // invalid size value used initially and also when size retrieval through PackageManager 74 // fails for whatever reason 75 private static final int SIZE_INVALID = -1; 76 77 // Result code identifiers 78 public static final int REQUEST_MANAGE_SPACE = 2; 79 80 private static final int DLG_CLEAR_DATA = DLG_BASE + 1; 81 private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2; 82 83 private static final String KEY_STORAGE_USED = "storage_used"; 84 private static final String KEY_CHANGE_STORAGE = "change_storage_button"; 85 private static final String KEY_STORAGE_SPACE = "storage_space"; 86 private static final String KEY_STORAGE_CATEGORY = "storage_category"; 87 88 private static final String KEY_TOTAL_SIZE = "total_size"; 89 private static final String KEY_APP_SIZE = "app_size"; 90 private static final String KEY_EXTERNAL_CODE_SIZE = "external_code_size"; 91 private static final String KEY_DATA_SIZE = "data_size"; 92 private static final String KEY_EXTERNAL_DATA_SIZE = "external_data_size"; 93 private static final String KEY_CACHE_SIZE = "cache_size"; 94 95 private static final String KEY_CLEAR_DATA = "clear_data_button"; 96 private static final String KEY_CLEAR_CACHE = "clear_cache_button"; 97 98 private static final String KEY_URI_CATEGORY = "uri_category"; 99 private static final String KEY_CLEAR_URI = "clear_uri_button"; 100 101 private Preference mTotalSize; 102 private Preference mAppSize; 103 private Preference mDataSize; 104 private Preference mExternalCodeSize; 105 private Preference mExternalDataSize; 106 107 // Views related to cache info 108 private Preference mCacheSize; 109 private Button mClearDataButton; 110 private Button mClearCacheButton; 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 mHaveSizes = false; 122 123 private long mLastCodeSize = -1; 124 private long mLastDataSize = -1; 125 private long mLastExternalCodeSize = -1; 126 private long mLastExternalDataSize = -1; 127 private long mLastCacheSize = -1; 128 private long mLastTotalSize = -1; 129 130 private ClearCacheObserver mClearCacheObserver; 131 private ClearUserDataObserver mClearDataObserver; 132 133 // Resource strings 134 private CharSequence mInvalidSizeStr; 135 private CharSequence mComputingStr; 136 137 private VolumeInfo[] mCandidates; 138 private AlertDialog.Builder mDialogBuilder; 139 140 @Override 141 public void onCreate(Bundle savedInstanceState) { 142 super.onCreate(savedInstanceState); 143 144 addPreferencesFromResource(R.xml.app_storage_settings); 145 setupViews(); 146 initMoveDialog(); 147 } 148 149 @Override 150 public void onResume() { 151 super.onResume(); 152 mState.requestSize(mPackageName, mUserId); 153 } 154 155 private void setupViews() { 156 mComputingStr = getActivity().getText(R.string.computing_size); 157 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 158 159 // Set default values on sizes 160 mTotalSize = findPreference(KEY_TOTAL_SIZE); 161 mAppSize = findPreference(KEY_APP_SIZE); 162 mDataSize = findPreference(KEY_DATA_SIZE); 163 mExternalCodeSize = findPreference(KEY_EXTERNAL_CODE_SIZE); 164 mExternalDataSize = findPreference(KEY_EXTERNAL_DATA_SIZE); 165 166 if (Environment.isExternalStorageEmulated()) { 167 PreferenceCategory category = (PreferenceCategory) findPreference(KEY_STORAGE_CATEGORY); 168 category.removePreference(mExternalCodeSize); 169 category.removePreference(mExternalDataSize); 170 } 171 mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_DATA)) 172 .findViewById(R.id.button); 173 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 mCacheSize = findPreference(KEY_CACHE_SIZE); 182 mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_CACHE)) 183 .findViewById(R.id.button); 184 mClearCacheButton.setText(R.string.clear_cache_btn_text); 185 186 // URI permissions section 187 mUri = (PreferenceCategory) findPreference(KEY_URI_CATEGORY); 188 mClearUri = (LayoutPreference) mUri.findPreference(KEY_CLEAR_URI); 189 mClearUriButton = (Button) mClearUri.findViewById(R.id.button); 190 mClearUriButton.setText(R.string.clear_uri_btn_text); 191 mClearUriButton.setOnClickListener(this); 192 } 193 194 @Override 195 public void onClick(View v) { 196 if (v == mClearCacheButton) { 197 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 198 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 199 getActivity(), mAppsControlDisallowedAdmin); 200 return; 201 } else if (mClearCacheObserver == null) { // Lazy initialization of observer 202 mClearCacheObserver = new ClearCacheObserver(); 203 } 204 mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 205 } else if (v == mClearDataButton) { 206 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 207 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 208 getActivity(), mAppsControlDisallowedAdmin); 209 } else if (mAppEntry.info.manageSpaceActivityName != null) { 210 if (!Utils.isMonkeyRunning()) { 211 Intent intent = new Intent(Intent.ACTION_DEFAULT); 212 intent.setClassName(mAppEntry.info.packageName, 213 mAppEntry.info.manageSpaceActivityName); 214 startActivityForResult(intent, REQUEST_MANAGE_SPACE); 215 } 216 } else { 217 showDialogInner(DLG_CLEAR_DATA, 0); 218 } 219 } else if (v == mChangeStorageButton && mDialogBuilder != null && !isMoveInProgress()) { 220 mDialogBuilder.show(); 221 } else if (v == mClearUriButton) { 222 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 223 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 224 getActivity(), mAppsControlDisallowedAdmin); 225 } else { 226 clearUriPermissions(); 227 } 228 } 229 } 230 231 private boolean isMoveInProgress() { 232 try { 233 // TODO: define a cleaner API for this 234 AppGlobals.getPackageManager().checkPackageStartable(mPackageName, 235 UserHandle.myUserId()); 236 return false; 237 } catch (RemoteException | SecurityException e) { 238 return true; 239 } 240 } 241 242 @Override 243 public void onClick(DialogInterface dialog, int which) { 244 final Context context = getActivity(); 245 246 // If not current volume, kick off move wizard 247 final VolumeInfo targetVol = mCandidates[which]; 248 final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume( 249 mAppEntry.info); 250 if (!Objects.equals(targetVol, currentVol)) { 251 final Intent intent = new Intent(context, StorageWizardMoveConfirm.class); 252 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId()); 253 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 254 startActivity(intent); 255 } 256 dialog.dismiss(); 257 } 258 259 private String getSizeStr(long size) { 260 if (size == SIZE_INVALID) { 261 return mInvalidSizeStr.toString(); 262 } 263 return Formatter.formatFileSize(getActivity(), size); 264 } 265 266 private void refreshSizeInfo() { 267 if (mAppEntry.size == ApplicationsState.SIZE_INVALID 268 || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) { 269 mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; 270 if (!mHaveSizes) { 271 mAppSize.setSummary(mComputingStr); 272 mDataSize.setSummary(mComputingStr); 273 mCacheSize.setSummary(mComputingStr); 274 mTotalSize.setSummary(mComputingStr); 275 } 276 mClearDataButton.setEnabled(false); 277 mClearCacheButton.setEnabled(false); 278 } else { 279 mHaveSizes = true; 280 long codeSize = mAppEntry.codeSize; 281 long dataSize = mAppEntry.dataSize; 282 if (Environment.isExternalStorageEmulated()) { 283 codeSize += mAppEntry.externalCodeSize; 284 dataSize += mAppEntry.externalDataSize; 285 } else { 286 if (mLastExternalCodeSize != mAppEntry.externalCodeSize) { 287 mLastExternalCodeSize = mAppEntry.externalCodeSize; 288 mExternalCodeSize.setSummary(getSizeStr(mAppEntry.externalCodeSize)); 289 } 290 if (mLastExternalDataSize != mAppEntry.externalDataSize) { 291 mLastExternalDataSize = mAppEntry.externalDataSize; 292 mExternalDataSize.setSummary(getSizeStr( mAppEntry.externalDataSize)); 293 } 294 } 295 if (mLastCodeSize != codeSize) { 296 mLastCodeSize = codeSize; 297 mAppSize.setSummary(getSizeStr(codeSize)); 298 } 299 if (mLastDataSize != dataSize) { 300 mLastDataSize = dataSize; 301 mDataSize.setSummary(getSizeStr(dataSize)); 302 } 303 long cacheSize = mAppEntry.cacheSize + mAppEntry.externalCacheSize; 304 if (mLastCacheSize != cacheSize) { 305 mLastCacheSize = cacheSize; 306 mCacheSize.setSummary(getSizeStr(cacheSize)); 307 } 308 if (mLastTotalSize != mAppEntry.size) { 309 mLastTotalSize = mAppEntry.size; 310 mTotalSize.setSummary(getSizeStr(mAppEntry.size)); 311 } 312 313 if ((mAppEntry.dataSize+ mAppEntry.externalDataSize) <= 0 || !mCanClearData) { 314 mClearDataButton.setEnabled(false); 315 } else { 316 mClearDataButton.setEnabled(true); 317 mClearDataButton.setOnClickListener(this); 318 } 319 if (cacheSize <= 0) { 320 mClearCacheButton.setEnabled(false); 321 } else { 322 mClearCacheButton.setEnabled(true); 323 mClearCacheButton.setOnClickListener(this); 324 } 325 } 326 if (mAppsControlDisallowedBySystem) { 327 mClearCacheButton.setEnabled(false); 328 mClearDataButton.setEnabled(false); 329 } 330 } 331 332 @Override 333 protected boolean refreshUi() { 334 retrieveAppEntry(); 335 if (mAppEntry == null) { 336 return false; 337 } 338 refreshSizeInfo(); 339 refreshGrantedUriPermissions(); 340 341 final VolumeInfo currentVol = getActivity().getPackageManager() 342 .getPackageCurrentVolume(mAppEntry.info); 343 final StorageManager storage = getContext().getSystemService(StorageManager.class); 344 mStorageUsed.setSummary(storage.getBestVolumeDescription(currentVol)); 345 346 refreshButtons(); 347 348 return true; 349 } 350 351 private void refreshButtons() { 352 initMoveDialog(); 353 initDataButtons(); 354 } 355 356 private void initDataButtons() { 357 // If the app doesn't have its own space management UI 358 // And it's a system app that doesn't allow clearing user data or is an active admin 359 // Then disable the Clear Data button. 360 if (mAppEntry.info.manageSpaceActivityName == null 361 && ((mAppEntry.info.flags&(ApplicationInfo.FLAG_SYSTEM 362 | ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)) 363 == ApplicationInfo.FLAG_SYSTEM 364 || mDpm.packageHasActiveAdmins(mPackageName))) { 365 mClearDataButton.setText(R.string.clear_user_data_text); 366 mClearDataButton.setEnabled(false); 367 mCanClearData = false; 368 } else { 369 if (mAppEntry.info.manageSpaceActivityName != null) { 370 mClearDataButton.setText(R.string.manage_space_text); 371 } else { 372 mClearDataButton.setText(R.string.clear_user_data_text); 373 } 374 mClearDataButton.setOnClickListener(this); 375 } 376 377 if (mAppsControlDisallowedBySystem) { 378 mClearDataButton.setEnabled(false); 379 } 380 } 381 382 private void initMoveDialog() { 383 final Context context = getActivity(); 384 final StorageManager storage = context.getSystemService(StorageManager.class); 385 386 final List<VolumeInfo> candidates = context.getPackageManager() 387 .getPackageCandidateVolumes(mAppEntry.info); 388 if (candidates.size() > 1) { 389 Collections.sort(candidates, VolumeInfo.getDescriptionComparator()); 390 391 CharSequence[] labels = new CharSequence[candidates.size()]; 392 int current = -1; 393 for (int i = 0; i < candidates.size(); i++) { 394 final String volDescrip = storage.getBestVolumeDescription(candidates.get(i)); 395 if (Objects.equals(volDescrip, mStorageUsed.getSummary())) { 396 current = i; 397 } 398 labels[i] = volDescrip; 399 } 400 mCandidates = candidates.toArray(new VolumeInfo[candidates.size()]); 401 mDialogBuilder = new AlertDialog.Builder(getContext()) 402 .setTitle(R.string.change_storage) 403 .setSingleChoiceItems(labels, current, this) 404 .setNegativeButton(R.string.cancel, null); 405 } else { 406 removePreference(KEY_STORAGE_USED); 407 removePreference(KEY_CHANGE_STORAGE); 408 removePreference(KEY_STORAGE_SPACE); 409 } 410 } 411 412 /* 413 * Private method to initiate clearing user data when the user clicks the clear data 414 * button for a system package 415 */ 416 private void initiateClearUserData() { 417 mClearDataButton.setEnabled(false); 418 // Invoke uninstall or clear user data based on sysPackage 419 String packageName = mAppEntry.info.packageName; 420 Log.i(TAG, "Clearing user data for package : " + packageName); 421 if (mClearDataObserver == null) { 422 mClearDataObserver = new ClearUserDataObserver(); 423 } 424 ActivityManager am = (ActivityManager) 425 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 426 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 427 if (!res) { 428 // Clearing data failed for some obscure reason. Just log error for now 429 Log.i(TAG, "Couldnt clear application user data for package:"+packageName); 430 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 431 } else { 432 mClearDataButton.setText(R.string.recompute_size); 433 } 434 } 435 436 /* 437 * Private method to handle clear message notification from observer when 438 * the async operation from PackageManager is complete 439 */ 440 private void processClearMsg(Message msg) { 441 int result = msg.arg1; 442 String packageName = mAppEntry.info.packageName; 443 mClearDataButton.setText(R.string.clear_user_data_text); 444 if (result == OP_SUCCESSFUL) { 445 Log.i(TAG, "Cleared user data for package : "+packageName); 446 mState.requestSize(mPackageName, mUserId); 447 } else { 448 mClearDataButton.setEnabled(true); 449 } 450 } 451 452 private void refreshGrantedUriPermissions() { 453 // Clear UI first (in case the activity has been resumed) 454 removeUriPermissionsFromUi(); 455 456 // Gets all URI permissions from am. 457 ActivityManager am = (ActivityManager) getActivity().getSystemService( 458 Context.ACTIVITY_SERVICE); 459 List<UriPermission> perms = 460 am.getGrantedUriPermissions(mAppEntry.info.packageName).getList(); 461 462 if (perms.isEmpty()) { 463 mClearUriButton.setVisibility(View.GONE); 464 return; 465 } 466 467 PackageManager pm = getActivity().getPackageManager(); 468 469 // Group number of URIs by app. 470 Map<CharSequence, MutableInt> uriCounters = new TreeMap<>(); 471 for (UriPermission perm : perms) { 472 String authority = perm.getUri().getAuthority(); 473 ProviderInfo provider = pm.resolveContentProvider(authority, 0); 474 CharSequence app = provider.applicationInfo.loadLabel(pm); 475 MutableInt count = uriCounters.get(app); 476 if (count == null) { 477 uriCounters.put(app, new MutableInt(1)); 478 } else { 479 count.value++; 480 } 481 } 482 483 // Dynamically add the preferences, one per app. 484 int order = 0; 485 for (Map.Entry<CharSequence, MutableInt> entry : uriCounters.entrySet()) { 486 int numberResources = entry.getValue().value; 487 Preference pref = new Preference(getPrefContext()); 488 pref.setTitle(entry.getKey()); 489 pref.setSummary(getPrefContext().getResources() 490 .getQuantityString(R.plurals.uri_permissions_text, numberResources, 491 numberResources)); 492 pref.setSelectable(false); 493 pref.setLayoutResource(R.layout.horizontal_preference); 494 pref.setOrder(order); 495 Log.v(TAG, "Adding preference '" + pref + "' at order " + order); 496 mUri.addPreference(pref); 497 } 498 499 if (mAppsControlDisallowedBySystem) { 500 mClearUriButton.setEnabled(false); 501 } 502 503 mClearUri.setOrder(order); 504 mClearUriButton.setVisibility(View.VISIBLE); 505 506 } 507 508 private void clearUriPermissions() { 509 // Synchronously revoke the permissions. 510 final ActivityManager am = (ActivityManager) getActivity().getSystemService( 511 Context.ACTIVITY_SERVICE); 512 am.clearGrantedUriPermissions(mAppEntry.info.packageName); 513 514 // Update UI 515 refreshGrantedUriPermissions(); 516 } 517 518 private void removeUriPermissionsFromUi() { 519 // Remove all preferences but the clear button. 520 int count = mUri.getPreferenceCount(); 521 for (int i = count - 1; i >= 0; i--) { 522 Preference pref = mUri.getPreference(i); 523 if (pref != mClearUri) { 524 mUri.removePreference(pref); 525 } 526 } 527 } 528 529 @Override 530 protected AlertDialog createDialog(int id, int errorCode) { 531 switch (id) { 532 case DLG_CLEAR_DATA: 533 return new AlertDialog.Builder(getActivity()) 534 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 535 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 536 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 537 public void onClick(DialogInterface dialog, int which) { 538 // Clear user data here 539 initiateClearUserData(); 540 } 541 }) 542 .setNegativeButton(R.string.dlg_cancel, null) 543 .create(); 544 case DLG_CANNOT_CLEAR_DATA: 545 return new AlertDialog.Builder(getActivity()) 546 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 547 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 548 .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 549 public void onClick(DialogInterface dialog, int which) { 550 mClearDataButton.setEnabled(false); 551 //force to recompute changed value 552 setIntentAndFinish(false, false); 553 } 554 }) 555 .create(); 556 } 557 return null; 558 } 559 560 @Override 561 public void onPackageSizeChanged(String packageName) { 562 if (packageName.equals(mAppEntry.info.packageName)) { 563 refreshSizeInfo(); 564 } 565 } 566 567 private final Handler mHandler = new Handler() { 568 public void handleMessage(Message msg) { 569 if (getView() == null) { 570 return; 571 } 572 switch (msg.what) { 573 case MSG_CLEAR_USER_DATA: 574 processClearMsg(msg); 575 break; 576 case MSG_CLEAR_CACHE: 577 // Refresh size info 578 mState.requestSize(mPackageName, mUserId); 579 break; 580 } 581 } 582 }; 583 584 public static CharSequence getSummary(AppEntry appEntry, Context context) { 585 if (appEntry.size == ApplicationsState.SIZE_INVALID 586 || appEntry.size == ApplicationsState.SIZE_UNKNOWN) { 587 return context.getText(R.string.computing_size); 588 } else { 589 CharSequence storageType = context.getString( 590 (appEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 591 ? R.string.storage_type_external 592 : R.string.storage_type_internal); 593 return context.getString(R.string.storage_summary_format, 594 getSize(appEntry, context), storageType); 595 } 596 } 597 598 private static CharSequence getSize(AppEntry appEntry, Context context) { 599 long size = appEntry.size; 600 if (size == SIZE_INVALID) { 601 return context.getText(R.string.invalid_size_value); 602 } 603 return Formatter.formatFileSize(context, size); 604 } 605 606 @Override 607 protected int getMetricsCategory() { 608 return MetricsEvent.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