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