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.settings.fuelgauge.datasaver.DynamicDenylistManager;
57 import com.android.settingslib.RestrictedLockUtils;
58 import com.android.settingslib.applications.AppUtils;
59 import com.android.settingslib.applications.ApplicationsState.Callbacks;
60 import com.android.settingslib.applications.StorageStatsSource;
61 import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
62 import com.android.settingslib.utils.StringUtil;
63 import com.android.settingslib.widget.ActionButtonsPreference;
64 import com.android.settingslib.widget.LayoutPreference;
65 
66 import java.util.Collections;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Objects;
70 import java.util.TreeMap;
71 
72 public class AppStorageSettings extends AppInfoWithHeader
73         implements OnClickListener, Callbacks, DialogInterface.OnClickListener,
74         LoaderManager.LoaderCallbacks<AppStorageStats> {
75     private static final String TAG = AppStorageSettings.class.getSimpleName();
76 
77     //internal constants used in Handler
78     private static final int OP_SUCCESSFUL = 1;
79     private static final int OP_FAILED = 2;
80     private static final int MSG_CLEAR_USER_DATA = 1;
81     private static final int MSG_CLEAR_CACHE = 3;
82 
83     // invalid size value used initially and also when size retrieval through PackageManager
84     // fails for whatever reason
85     private static final int SIZE_INVALID = -1;
86 
87     // Result code identifiers
88     public static final int REQUEST_MANAGE_SPACE = 2;
89 
90     private static final int DLG_CLEAR_DATA = DLG_BASE + 1;
91     private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2;
92 
93     private static final String KEY_STORAGE_USED = "storage_used";
94     private static final String KEY_CHANGE_STORAGE = "change_storage_button";
95     private static final String KEY_STORAGE_SPACE = "storage_space";
96     private static final String KEY_STORAGE_CATEGORY = "storage_category";
97 
98     private static final String KEY_TOTAL_SIZE = "total_size";
99     private static final String KEY_APP_SIZE = "app_size";
100     private static final String KEY_DATA_SIZE = "data_size";
101     private static final String KEY_CACHE_SIZE = "cache_size";
102 
103     private static final String KEY_HEADER_BUTTONS = "header_view";
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 static final String KEY_CACHE_CLEARED = "cache_cleared";
109     private static final String KEY_DATA_CLEARED = "data_cleared";
110 
111     // Views related to cache info
112     @VisibleForTesting
113     ActionButtonsPreference mButtonsPref;
114 
115     private Preference mStorageUsed;
116     private Button mChangeStorageButton;
117 
118     // Views related to URI permissions
119     private Button mClearUriButton;
120     private LayoutPreference mClearUri;
121     private PreferenceCategory mUri;
122 
123     private boolean mCanClearData = true;
124     private boolean mCacheCleared;
125     private boolean mDataCleared;
126 
127     @VisibleForTesting
128     AppStorageSizesController mSizeController;
129 
130     private ClearCacheObserver mClearCacheObserver;
131     private ClearUserDataObserver mClearDataObserver;
132 
133     private VolumeInfo[] mCandidates;
134     private AlertDialog.Builder mDialogBuilder;
135     private ApplicationInfo mInfo;
136 
137     @Override
onCreate(Bundle savedInstanceState)138     public void onCreate(Bundle savedInstanceState) {
139         super.onCreate(savedInstanceState);
140         if (savedInstanceState != null) {
141             mCacheCleared = savedInstanceState.getBoolean(KEY_CACHE_CLEARED, false);
142             mDataCleared = savedInstanceState.getBoolean(KEY_DATA_CLEARED, false);
143             mCacheCleared = mCacheCleared || mDataCleared;
144         }
145 
146         addPreferencesFromResource(R.xml.app_storage_settings);
147         setupViews();
148         initMoveDialog();
149     }
150 
151     @Override
onResume()152     public void onResume() {
153         super.onResume();
154         updateSize();
155     }
156 
157     @Override
onSaveInstanceState(Bundle outState)158     public void onSaveInstanceState(Bundle outState) {
159         super.onSaveInstanceState(outState);
160         outState.putBoolean(KEY_CACHE_CLEARED, mCacheCleared);
161         outState.putBoolean(KEY_DATA_CLEARED, mDataCleared);
162     }
163 
setupViews()164     private void setupViews() {
165         // Set default values on sizes
166         mSizeController = new AppStorageSizesController.Builder()
167                 .setTotalSizePreference(findPreference(KEY_TOTAL_SIZE))
168                 .setAppSizePreference(findPreference(KEY_APP_SIZE))
169                 .setDataSizePreference(findPreference(KEY_DATA_SIZE))
170                 .setCacheSizePreference(findPreference(KEY_CACHE_SIZE))
171                 .setComputingString(R.string.computing_size)
172                 .setErrorString(R.string.invalid_size_value)
173                 .build();
174         mButtonsPref = ((ActionButtonsPreference) findPreference(KEY_HEADER_BUTTONS));
175         mStorageUsed = findPreference(KEY_STORAGE_USED);
176         mChangeStorageButton = (Button) ((LayoutPreference) findPreference(KEY_CHANGE_STORAGE))
177                 .findViewById(R.id.button);
178         mChangeStorageButton.setText(R.string.change);
179         mChangeStorageButton.setOnClickListener(this);
180 
181         // Cache section
182         mButtonsPref
183                 .setButton2Text(R.string.clear_cache_btn_text)
184                 .setButton2Icon(R.drawable.ic_settings_delete);
185 
186         // URI permissions section
187         mUri = (PreferenceCategory) findPreference(KEY_URI_CATEGORY);
188         mClearUri = (LayoutPreference) mUri.findPreference(KEY_CLEAR_URI);
189         mClearUriButton = (Button) mClearUri.findViewById(R.id.button);
190         mClearUriButton.setText(R.string.clear_uri_btn_text);
191         mClearUriButton.setOnClickListener(this);
192     }
193 
194     @VisibleForTesting
handleClearCacheClick()195     void handleClearCacheClick() {
196         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
197             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
198                     getActivity(), mAppsControlDisallowedAdmin);
199             return;
200         } else if (mClearCacheObserver == null) { // Lazy initialization of observer
201             mClearCacheObserver = new ClearCacheObserver();
202         }
203         mMetricsFeatureProvider.action(getContext(),
204                 SettingsEnums.ACTION_SETTINGS_CLEAR_APP_CACHE);
205         mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver);
206     }
207 
208     @VisibleForTesting
handleClearDataClick()209     void handleClearDataClick() {
210         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
211             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
212                     getActivity(), mAppsControlDisallowedAdmin);
213         } else if (mAppEntry.info.manageSpaceActivityName != null) {
214             if (!Utils.isMonkeyRunning()) {
215                 Intent intent = new Intent(Intent.ACTION_DEFAULT);
216                 intent.setClassName(mAppEntry.info.packageName,
217                         mAppEntry.info.manageSpaceActivityName);
218                 startActivityForResult(intent, REQUEST_MANAGE_SPACE);
219             }
220         } else {
221             showDialogInner(DLG_CLEAR_DATA, 0);
222         }
223     }
224 
225     @Override
onClick(View v)226     public void onClick(View v) {
227         if (v == mChangeStorageButton && mDialogBuilder != null && !isMoveInProgress()) {
228             mDialogBuilder.show();
229         } else if (v == mClearUriButton) {
230             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
231                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
232                         getActivity(), mAppsControlDisallowedAdmin);
233             } else {
234                 clearUriPermissions();
235             }
236         }
237     }
238 
isMoveInProgress()239     private boolean isMoveInProgress() {
240         try {
241             // TODO: define a cleaner API for this
242             AppGlobals.getPackageManager().checkPackageStartable(mPackageName,
243                     UserHandle.myUserId());
244             return false;
245         } catch (RemoteException | SecurityException e) {
246             return true;
247         }
248     }
249 
250     @Override
onClick(DialogInterface dialog, int which)251     public void onClick(DialogInterface dialog, int which) {
252         final Context context = getActivity();
253 
254         // If not current volume, kick off move wizard
255         final VolumeInfo targetVol = mCandidates[which];
256         final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume(
257                 mAppEntry.info);
258         if (!Objects.equals(targetVol, currentVol)) {
259             final Intent intent = new Intent(context, StorageWizardMoveConfirm.class);
260             intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId());
261             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
262             startActivity(intent);
263         }
264         dialog.dismiss();
265     }
266 
267     @Override
refreshUi()268     protected boolean refreshUi() {
269         retrieveAppEntry();
270         if (mAppEntry == null) {
271             return false;
272         }
273         updateUiWithSize(mSizeController.getLastResult());
274         refreshGrantedUriPermissions();
275 
276         final VolumeInfo currentVol = getActivity().getPackageManager()
277                 .getPackageCurrentVolume(mAppEntry.info);
278         final StorageManager storage = getContext().getSystemService(StorageManager.class);
279         mStorageUsed.setSummary(storage.getBestVolumeDescription(currentVol));
280 
281         refreshButtons();
282 
283         return true;
284     }
285 
refreshButtons()286     private void refreshButtons() {
287         initMoveDialog();
288         initDataButtons();
289     }
290 
initDataButtons()291     private void initDataButtons() {
292         final boolean appHasSpaceManagementUI = mAppEntry.info.manageSpaceActivityName != null;
293         final boolean appHasActiveAdmins = mDpm.packageHasActiveAdmins(mPackageName);
294         // Check that SYSTEM_APP flag is set, and ALLOW_CLEAR_USER_DATA is not set.
295         final boolean isNonClearableSystemApp =
296                 (mAppEntry.info.flags & (FLAG_SYSTEM | FLAG_ALLOW_CLEAR_USER_DATA)) == FLAG_SYSTEM;
297         final boolean appRestrictsClearingData = isNonClearableSystemApp || appHasActiveAdmins;
298 
299         final Intent intent = new Intent(Intent.ACTION_DEFAULT);
300         if (appHasSpaceManagementUI) {
301             intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName);
302         }
303         final boolean isManageSpaceActivityAvailable =
304                 getPackageManager().resolveActivity(intent, 0) != null;
305 
306         if ((!appHasSpaceManagementUI && appRestrictsClearingData)
307                 || !isManageSpaceActivityAvailable) {
308             mButtonsPref
309                     .setButton1Text(R.string.clear_user_data_text)
310                     .setButton1Icon(R.drawable.ic_settings_delete)
311                     .setButton1Enabled(false);
312             mCanClearData = false;
313         } else {
314             mButtonsPref.setButton1Text(R.string.clear_user_data_text);
315             mButtonsPref.setButton1Icon(R.drawable.ic_settings_delete)
316                     .setButton1OnClickListener(v -> handleClearDataClick());
317         }
318 
319         if (mAppsControlDisallowedBySystem || AppUtils.isMainlineModule(mPm, mPackageName)) {
320             mButtonsPref.setButton1Enabled(false);
321         }
322     }
323 
initMoveDialog()324     private void initMoveDialog() {
325         final Context context = getActivity();
326         final StorageManager storage = context.getSystemService(StorageManager.class);
327 
328         final List<VolumeInfo> candidates = context.getPackageManager()
329                 .getPackageCandidateVolumes(mAppEntry.info);
330         if (candidates.size() > 1) {
331             Collections.sort(candidates, VolumeInfo.getDescriptionComparator());
332 
333             CharSequence[] labels = new CharSequence[candidates.size()];
334             int current = -1;
335             for (int i = 0; i < candidates.size(); i++) {
336                 final String volDescrip = storage.getBestVolumeDescription(candidates.get(i));
337                 if (Objects.equals(volDescrip, mStorageUsed.getSummary())) {
338                     current = i;
339                 }
340                 labels[i] = volDescrip;
341             }
342             mCandidates = candidates.toArray(new VolumeInfo[candidates.size()]);
343             mDialogBuilder = new AlertDialog.Builder(getContext())
344                     .setTitle(R.string.change_storage)
345                     .setSingleChoiceItems(labels, current, this)
346                     .setNegativeButton(R.string.cancel, null);
347         } else {
348             removePreference(KEY_STORAGE_USED);
349             removePreference(KEY_CHANGE_STORAGE);
350             removePreference(KEY_STORAGE_SPACE);
351         }
352     }
353 
354     /*
355      * Private method to initiate clearing user data when the user clicks the clear data
356      * button for a system package
357      */
initiateClearUserData()358     private void initiateClearUserData() {
359         mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_SETTINGS_CLEAR_APP_DATA);
360         mButtonsPref.setButton1Enabled(false);
361         // Invoke uninstall or clear user data based on sysPackage
362         String packageName = mAppEntry.info.packageName;
363         DynamicDenylistManager.getInstance(getContext())
364                 .resetDenylistIfNeeded(packageName, /* force= */ false);
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(StringUtil.getIcuPluralsString(mUri.getContext(), numberResources,
446                     R.string.uri_permissions_text));
447             pref.setSelectable(false);
448             pref.setLayoutResource(R.layout.horizontal_preference);
449             pref.setOrder(order);
450             Log.v(TAG, "Adding preference '" + pref + "' at order " + order);
451             mUri.addPreference(pref);
452         }
453 
454         if (mAppsControlDisallowedBySystem) {
455             mClearUriButton.setEnabled(false);
456         }
457 
458         mClearUri.setOrder(order);
459         mClearUriButton.setVisibility(View.VISIBLE);
460 
461     }
462 
clearUriPermissions()463     private void clearUriPermissions() {
464         final Context context = getActivity();
465         final String packageName = mAppEntry.info.packageName;
466         // Synchronously revoke the permissions.
467         final ActivityManager am = (ActivityManager) context.getSystemService(
468                 Context.ACTIVITY_SERVICE);
469         am.clearGrantedUriPermissions(packageName);
470 
471         // Update UI
472         refreshGrantedUriPermissions();
473     }
474 
removeUriPermissionsFromUi()475     private void removeUriPermissionsFromUi() {
476         // Remove all preferences but the clear button.
477         int count = mUri.getPreferenceCount();
478         for (int i = count - 1; i >= 0; i--) {
479             Preference pref = mUri.getPreference(i);
480             if (pref != mClearUri) {
481                 mUri.removePreference(pref);
482             }
483         }
484     }
485 
486     @Override
createDialog(int id, int errorCode)487     protected AlertDialog createDialog(int id, int errorCode) {
488         switch (id) {
489             case DLG_CLEAR_DATA:
490                 return new AlertDialog.Builder(getActivity())
491                         .setTitle(getActivity().getText(R.string.clear_data_dlg_title))
492                         .setMessage(getActivity().getText(R.string.clear_data_dlg_text))
493                         .setPositiveButton(R.string.dlg_delete,
494                                 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