• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AppDeletionType( DeletionHelperSettings fragment, HashSet<String> checkedApplications, int thresholdType)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
registerFreeableChangedListener(FreeableChangedListener listener)78     public void registerFreeableChangedListener(FreeableChangedListener listener) {
79         mListener = listener;
80     }
81 
82     @Override
onResume()83     public void onResume() {
84 
85     }
86 
87     @Override
onPause()88     public void onPause() {
89 
90     }
91 
92     @Override
onSaveInstanceStateBundle(Bundle savedInstanceState)93     public void onSaveInstanceStateBundle(Bundle savedInstanceState) {
94         savedInstanceState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
95     }
96 
97     @Override
clearFreeableData(Activity activity)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      */
registerView(AppDeletionPreferenceGroup preference)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      */
setChecked(String packageName, boolean isChecked)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      */
getTotalAppsFreeableSpace(boolean countUnchecked)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      */
isChecked(String packageName)174     public boolean isChecked(String packageName) {
175         return mCheckedApplications.contains(packageName);
176     }
177 
getFilter(int mThresholdType)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 
maybeNotifyListener()188     private void maybeNotifyListener() {
189         if (mListener != null) {
190             mListener.onFreeableChanged(
191                     mApps.size(),
192                     getTotalAppsFreeableSpace(DeletionHelperSettings.COUNT_CHECKED_ONLY));
193         }
194     }
195 
getDeletionThreshold()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
getLoadingStatus()209     public int getLoadingStatus() {
210         return mLoadingStatus;
211     }
212 
213     @Override
getContentCount()214     public int getContentCount() {
215         return mApps.size();
216     }
217 
218     @Override
setLoadingStatus(@oadingStatus int loadingStatus)219     public void setLoadingStatus(@LoadingStatus int loadingStatus) {
220         mLoadingStatus = loadingStatus;
221     }
222 
223     @Override
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data)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
onLoaderReset(Loader<List<PackageInfo>> loader)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          */
onAppRebuild(List<PackageInfo> apps)258         void onAppRebuild(List<PackageInfo> apps);
259     }
260 }
261