Home | History | Annotate | Download | only in deletionhelper
      1 /*
      2  * Copyright (C) 2016 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.storagemanager.deletionhelper;
     18 
     19 import android.app.Activity;
     20 import android.app.LoaderManager;
     21 import android.app.usage.UsageStatsManager;
     22 import android.content.Context;
     23 import android.content.Loader;
     24 import android.os.Bundle;
     25 import android.os.UserHandle;
     26 import android.os.storage.VolumeInfo;
     27 import android.util.ArraySet;
     28 import android.util.Log;
     29 import com.android.internal.logging.MetricsLogger;
     30 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     31 import com.android.settingslib.applications.StorageStatsSource;
     32 import com.android.settingslib.wrapper.PackageManagerWrapper;
     33 import com.android.storagemanager.deletionhelper.AppsAsyncLoader.AppFilter;
     34 import com.android.storagemanager.deletionhelper.AppsAsyncLoader.PackageInfo;
     35 import java.util.HashSet;
     36 import java.util.List;
     37 
     38 /**
     39  * AppDeletionType provides a list of apps which have not been used for a while on the system. It
     40  * also provides the functionality to clear out these apps.
     41  */
     42 public class AppDeletionType
     43         implements LoaderManager.LoaderCallbacks<List<PackageInfo>>, DeletionType {
     44     public static final String EXTRA_CHECKED_SET = "checkedSet";
     45     private static final String TAG = "AppDeletionType";
     46     private static final int LOADER_ID = 25;
     47     public static final String THRESHOLD_TYPE_KEY = "threshold_type";
     48     public static final int BUNDLE_CAPACITY = 1;
     49 
     50     private FreeableChangedListener mListener;
     51     private AppListener mAppListener;
     52     private HashSet<String> mCheckedApplications;
     53     private Context mContext;
     54     private int mThresholdType;
     55     private List<PackageInfo> mApps;
     56     private int mLoadingStatus;
     57 
     58     public AppDeletionType(
     59             DeletionHelperSettings fragment,
     60             HashSet<String> checkedApplications,
     61             int thresholdType) {
     62         mLoadingStatus = LoadingStatus.LOADING;
     63         mThresholdType = thresholdType;
     64         mContext = fragment.getContext();
     65         if (checkedApplications != null) {
     66             mCheckedApplications = checkedApplications;
     67         } else {
     68             mCheckedApplications = new HashSet<>();
     69         }
     70         Bundle bundle = new Bundle(BUNDLE_CAPACITY);
     71         bundle.putInt(THRESHOLD_TYPE_KEY, mThresholdType);
     72         // NOTE: This is not responsive to package changes. Bug filed for seeing if feature is
     73         // necessary b/35065979
     74         fragment.getLoaderManager().initLoader(LOADER_ID, bundle, this);
     75     }
     76 
     77     @Override
     78     public void registerFreeableChangedListener(FreeableChangedListener listener) {
     79         mListener = listener;
     80     }
     81 
     82     @Override
     83     public void onResume() {
     84 
     85     }
     86 
     87     @Override
     88     public void onPause() {
     89 
     90     }
     91 
     92     @Override
     93     public void onSaveInstanceStateBundle(Bundle savedInstanceState) {
     94         savedInstanceState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
     95     }
     96 
     97     @Override
     98     public void clearFreeableData(Activity activity) {
     99         if (mApps == null) {
    100             return;
    101         }
    102 
    103         ArraySet<String> apps = new ArraySet<>();
    104         for (PackageInfo app : mApps) {
    105             final String packageName = app.packageName;
    106             if (mCheckedApplications.contains(packageName)) {
    107                 apps.add(packageName);
    108             }
    109         }
    110         // TODO: If needed, add an action on the callback.
    111         PackageDeletionTask task = new PackageDeletionTask(activity.getPackageManager(), apps,
    112                 new PackageDeletionTask.Callback() {
    113                     @Override
    114                     public void onSuccess() {
    115                     }
    116 
    117                     @Override
    118                     public void onError() {
    119                         Log.e(TAG, "An error occurred while uninstalling packages.");
    120                         MetricsLogger.action(activity,
    121                                 MetricsEvent.ACTION_DELETION_HELPER_APPS_DELETION_FAIL);
    122                     }
    123                 });
    124 
    125         task.run();
    126     }
    127 
    128     /**
    129      * Registers a preference group view to notify when the app list changes.
    130      */
    131     public void registerView(AppDeletionPreferenceGroup preference) {
    132         mAppListener = preference;
    133     }
    134 
    135     /**
    136      * Set a package to be checked for deletion, if the apps are cleared.
    137      * @param packageName The name of the package to potentially delete.
    138      * @param isChecked Whether or not the package should be deleted.
    139      */
    140     public void setChecked(String packageName, boolean isChecked) {
    141         if (isChecked) {
    142             mCheckedApplications.add(packageName);
    143         } else {
    144             mCheckedApplications.remove(packageName);
    145         }
    146         maybeNotifyListener();
    147     }
    148 
    149     /**
    150      * Returns an amount of clearable app data.
    151      * @param countUnchecked If unchecked applications should be counted for size purposes.
    152      */
    153     public long getTotalAppsFreeableSpace(boolean countUnchecked) {
    154         long freeableSpace = 0;
    155         if (mApps != null) {
    156             for (int i = 0, size = mApps.size(); i < size; i++) {
    157                 final PackageInfo app = mApps.get(i);
    158                 long appSize = app.size;
    159                 final String packageName = app.packageName;
    160                 // If the appSize is negative, it is either an unknown size or an error occurred.
    161                 if ((countUnchecked || mCheckedApplications.contains(packageName)) && appSize > 0) {
    162                     freeableSpace += appSize;
    163                 }
    164             }
    165         }
    166 
    167         return freeableSpace;
    168     }
    169 
    170     /**
    171      * Returns if a given package is slated for deletion.
    172      * @param packageName The name of the package to check.
    173      */
    174     public boolean isChecked(String packageName) {
    175         return mCheckedApplications.contains(packageName);
    176     }
    177 
    178     private AppFilter getFilter(int mThresholdType) {
    179         switch (mThresholdType) {
    180             case AppsAsyncLoader.NO_THRESHOLD:
    181                 return AppsAsyncLoader.FILTER_NO_THRESHOLD;
    182             case AppsAsyncLoader.NORMAL_THRESHOLD:
    183             default:
    184                 return AppsAsyncLoader.FILTER_USAGE_STATS;
    185         }
    186     }
    187 
    188     private void maybeNotifyListener() {
    189         if (mListener != null) {
    190             mListener.onFreeableChanged(
    191                     mApps.size(),
    192                     getTotalAppsFreeableSpace(DeletionHelperSettings.COUNT_CHECKED_ONLY));
    193         }
    194     }
    195 
    196     public long getDeletionThreshold() {
    197         switch (mThresholdType) {
    198             case AppsAsyncLoader.NO_THRESHOLD:
    199                 // The threshold is actually Long.MIN_VALUE but we don't want to display that to
    200                 // the user.
    201                 return 0;
    202             case AppsAsyncLoader.NORMAL_THRESHOLD:
    203             default:
    204                 return AppsAsyncLoader.UNUSED_DAYS_DELETION_THRESHOLD;
    205         }
    206     }
    207 
    208     @Override
    209     public int getLoadingStatus() {
    210         return mLoadingStatus;
    211     }
    212 
    213     @Override
    214     public int getContentCount() {
    215         return mApps.size();
    216     }
    217 
    218     @Override
    219     public void setLoadingStatus(@LoadingStatus int loadingStatus) {
    220         mLoadingStatus = loadingStatus;
    221     }
    222 
    223     @Override
    224     public Loader<List<PackageInfo>> onCreateLoader(int id, Bundle args) {
    225         return new AppsAsyncLoader.Builder(mContext)
    226                 .setUid(UserHandle.myUserId())
    227                 .setUuid(VolumeInfo.ID_PRIVATE_INTERNAL)
    228                 .setStorageStatsSource(new StorageStatsSource(mContext))
    229                 .setPackageManager(new PackageManagerWrapper(mContext.getPackageManager()))
    230                 .setUsageStatsManager(
    231                         (UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE))
    232                 .setFilter(
    233                         getFilter(
    234                                 args.getInt(THRESHOLD_TYPE_KEY, AppsAsyncLoader.NORMAL_THRESHOLD)))
    235                 .build();
    236     }
    237 
    238     @Override
    239     public void onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data) {
    240         mApps = data;
    241         updateLoadingStatus();
    242         maybeNotifyListener();
    243         mAppListener.onAppRebuild(mApps);
    244     }
    245 
    246     @Override
    247     public void onLoaderReset(Loader<List<PackageInfo>> loader) {}
    248 
    249     /**
    250      * An interface for listening for when the app list has been rebuilt.
    251      */
    252     public interface AppListener {
    253         /**
    254          * Callback to be called once the app list is rebuilt.
    255          *
    256          * @param apps A list of eligible, clearable AppEntries.
    257          */
    258         void onAppRebuild(List<PackageInfo> apps);
    259     }
    260 }
    261