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