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