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