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