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