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