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.Application; 21 import android.os.Bundle; 22 import android.util.ArraySet; 23 import android.util.Log; 24 import com.android.internal.logging.MetricsLogger; 25 import com.android.internal.logging.MetricsProto.MetricsEvent; 26 import com.android.settingslib.applications.ApplicationsState; 27 28 import java.util.ArrayList; 29 import java.util.HashSet; 30 import java.util.List; 31 32 /** 33 * AppDeletionType provides a list of apps which have not been used for a while on the system. 34 * It also provides the functionality to clear out these apps. 35 */ 36 public class AppDeletionType implements DeletionType, ApplicationsState.Callbacks, 37 AppStateBaseBridge.Callback { 38 public static final String EXTRA_CHECKED_SET = "checkedSet"; 39 private static final String TAG = "AppDeletionType"; 40 41 private FreeableChangedListener mListener; 42 private AppListener mAppListener; 43 private List<ApplicationsState.AppEntry> mAppEntries; 44 private ApplicationsState mState; 45 private ApplicationsState.Session mSession; 46 private HashSet<String> mCheckedApplications; 47 private AppStateUsageStatsBridge mDataUsageBridge; 48 49 public AppDeletionType(Application app, HashSet<String> checkedApplications) { 50 mState = ApplicationsState.getInstance(app); 51 mSession = mState.newSession(this); 52 if (checkedApplications != null) { 53 mCheckedApplications = checkedApplications; 54 } else { 55 mCheckedApplications = new HashSet<>(); 56 } 57 mDataUsageBridge = new AppStateUsageStatsBridge(app.getApplicationContext(), mState, this); 58 } 59 60 @Override 61 public void registerFreeableChangedListener(FreeableChangedListener listener) { 62 mListener = listener; 63 } 64 65 @Override 66 public void onResume() { 67 mSession.resume(); 68 mDataUsageBridge.resume(); 69 } 70 71 @Override 72 public void onPause() { 73 mSession.pause(); 74 mDataUsageBridge.pause(); 75 } 76 77 @Override 78 public void onSaveInstanceStateBundle(Bundle savedInstanceState) { 79 savedInstanceState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications); 80 } 81 82 @Override 83 public void clearFreeableData(Activity activity) { 84 ArraySet<String> apps = new ArraySet<>(); 85 for (ApplicationsState.AppEntry entry : mAppEntries) { 86 final String packageName; 87 synchronized (entry) { 88 packageName = entry.info.packageName; 89 } 90 if (mCheckedApplications.contains(packageName)) { 91 apps.add(packageName); 92 } 93 } 94 // TODO: If needed, add an action on the callback. 95 PackageDeletionTask task = new PackageDeletionTask(activity.getPackageManager(), apps, 96 new PackageDeletionTask.Callback() { 97 @Override 98 public void onSuccess() { 99 } 100 101 @Override 102 public void onError() { 103 Log.e(TAG, "An error occurred while uninstalling packages."); 104 MetricsLogger.action(activity, 105 MetricsEvent.ACTION_DELETION_HELPER_APPS_DELETION_FAIL); 106 } 107 }); 108 109 task.run(); 110 } 111 112 @Override 113 public void onRunningStateChanged(boolean running) { 114 115 } 116 117 @Override 118 public void onPackageListChanged() { 119 } 120 121 @Override 122 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 123 if (apps == null) { 124 return; 125 } 126 mAppEntries = apps; 127 for (ApplicationsState.AppEntry app : mAppEntries) { 128 synchronized (app) { 129 mState.ensureIcon(app); 130 } 131 } 132 if (mAppListener != null) { 133 mAppListener.onAppRebuild(mAppEntries); 134 } 135 maybeNotifyListener(); 136 } 137 138 @Override 139 public void onPackageIconChanged() { 140 } 141 142 @Override 143 public void onPackageSizeChanged(String packageName) { 144 rebuild(); 145 } 146 147 @Override 148 public void onAllSizesComputed() { 149 rebuild(); 150 } 151 152 @Override 153 public void onLauncherInfoChanged() { 154 155 } 156 157 @Override 158 public void onLoadEntriesCompleted() { 159 rebuild(); 160 } 161 162 @Override 163 public void onExtraInfoUpdated() { 164 rebuild(); 165 } 166 167 /** 168 * Registers a preference group view to notify when the app list changes. 169 */ 170 public void registerView(AppDeletionPreferenceGroup preference) { 171 mAppListener = preference; 172 } 173 174 /** 175 * Set a package to be checked for deletion, if the apps are cleared. 176 * @param packageName The name of the package to potentially delete. 177 * @param isChecked Whether or not the package should be deleted. 178 */ 179 public void setChecked(String packageName, boolean isChecked) { 180 if (isChecked) { 181 mCheckedApplications.add(packageName); 182 } else { 183 mCheckedApplications.remove(packageName); 184 } 185 maybeNotifyListener(); 186 } 187 188 /** 189 * Returns an amount of clearable app data. 190 * @param countUnchecked If unchecked applications should be counted for size purposes. 191 */ 192 public long getTotalAppsFreeableSpace(boolean countUnchecked) { 193 long freeableSpace = 0; 194 if (mAppEntries != null) { 195 for (int i = 0; i < mAppEntries.size(); i++) { 196 final ApplicationsState.AppEntry entry = mAppEntries.get(i); 197 long entrySize = entry.size; 198 final String packageName; 199 synchronized (entry) { 200 packageName = entry.info.packageName; 201 } 202 // If the entrySize is negative, it is either an unknown size or an error occurred. 203 if ((countUnchecked || 204 mCheckedApplications.contains(packageName)) && entrySize > 0) { 205 freeableSpace += entrySize; 206 } 207 } 208 } 209 210 return freeableSpace; 211 } 212 213 /** 214 * Returns a number of eligible, clearable apps. 215 */ 216 public int getEligibleApps() { 217 if (mAppEntries == null) { 218 return 0; 219 } 220 return mAppEntries.size(); 221 } 222 223 /** 224 * Returns if a given package is slated for deletion. 225 * @param packageName The name of the package to check. 226 */ 227 public boolean isChecked(String packageName) { 228 return mCheckedApplications.contains(packageName); 229 } 230 231 private void rebuild() { 232 mSession.rebuild(AppStateUsageStatsBridge.FILTER_USAGE_STATS, 233 ApplicationsState.SIZE_COMPARATOR); 234 } 235 236 private void maybeNotifyListener() { 237 if (mListener != null) { 238 mListener.onFreeableChanged(mAppEntries.size(), getTotalAppsFreeableSpace(true)); 239 } 240 } 241 242 /** 243 * An interface for listening for when the app list has been rebuilt. 244 */ 245 public interface AppListener { 246 /** 247 * Callback to be called once the app list is rebuilt. 248 * @param apps A list of eligible, clearable AppEntries. 249 */ 250 void onAppRebuild(List<ApplicationsState.AppEntry> apps); 251 } 252 } 253