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 package com.android.storagemanager.deletionhelper; 17 18 import android.app.usage.UsageStats; 19 import android.app.usage.UsageStatsManager; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.os.SystemProperties; 26 import android.os.UserHandle; 27 import android.util.Log; 28 import com.android.storagemanager.deletionhelper.AppStateBaseBridge; 29 import com.android.storagemanager.deletionhelper.AppStateBaseBridge.Callback; 30 import com.android.settingslib.applications.ApplicationsState; 31 import com.android.settingslib.applications.ApplicationsState.AppEntry; 32 import com.android.settingslib.applications.ApplicationsState.AppFilter; 33 34 import java.util.ArrayList; 35 import java.util.Map; 36 import java.util.concurrent.TimeUnit; 37 38 /** 39 * Connects data from the UsageStatsManager to the ApplicationsState. 40 */ 41 public class AppStateUsageStatsBridge extends AppStateBaseBridge { 42 private static final String TAG = "AppStateUsageStatsBridge"; 43 44 private static final String DEBUG_APP_UNUSED_OVERRIDE = "debug.asm.app_unused_limit"; 45 46 private UsageStatsManager mUsageStatsManager; 47 private PackageManager mPm; 48 public static final long NEVER_USED = Long.MAX_VALUE; 49 public static final long UNKNOWN_LAST_USE = -1; 50 public static final long UNUSED_DAYS_DELETION_THRESHOLD = 90; 51 52 public AppStateUsageStatsBridge(Context context, ApplicationsState appState, 53 Callback callback) { 54 super(appState, callback); 55 mUsageStatsManager = 56 (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); 57 mPm = context.getPackageManager(); 58 } 59 60 @Override 61 protected void loadAllExtraInfo() { 62 ArrayList<AppEntry> apps = mAppSession.getAllApps(); 63 if (apps == null) return; 64 65 final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(0, 66 System.currentTimeMillis()); 67 for (AppEntry entry : apps) { 68 UsageStats usageStats = map.get(entry.info.packageName); 69 entry.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats), 70 getDaysSinceInstalled(entry.info.packageName), 71 UserHandle.getUserId(entry.info.uid)); 72 } 73 } 74 75 @Override 76 protected void updateExtraInfo(AppEntry app, String pkg, int uid) { 77 Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(0, 78 System.currentTimeMillis()); 79 UsageStats usageStats = map.get(app.info.packageName); 80 app.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats), 81 getDaysSinceInstalled(app.info.packageName), 82 UserHandle.getUserId(app.info.uid)); 83 } 84 85 private long getDaysSinceLastUse(UsageStats stats) { 86 if (stats == null) { 87 return NEVER_USED; 88 } 89 long lastUsed = stats.getLastTimeUsed(); 90 // Sometimes, a usage is recorded without a time and we don't know when the use was. 91 if (lastUsed <= 0) { 92 return UNKNOWN_LAST_USE; 93 } 94 return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUsed); 95 } 96 97 private long getDaysSinceInstalled(String packageName) { 98 PackageInfo pi = null; 99 try { 100 pi = mPm.getPackageInfo(packageName, 0); 101 } catch (PackageManager.NameNotFoundException e) { 102 Log.e(TAG, packageName + " was not found."); 103 } 104 105 if (pi == null) { 106 return UNKNOWN_LAST_USE; 107 } 108 109 return (TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - pi.firstInstallTime)); 110 } 111 112 /** 113 * Filters only non-system apps which haven't been used in the last 60 days. If an app's last 114 * usage is unknown, it is skipped. 115 */ 116 public static final AppFilter FILTER_USAGE_STATS = new AppFilter() { 117 private long mUnusedDaysThreshold; 118 119 @Override 120 public void init() { 121 mUnusedDaysThreshold = SystemProperties.getLong(DEBUG_APP_UNUSED_OVERRIDE, 122 UNUSED_DAYS_DELETION_THRESHOLD); 123 } 124 125 @Override 126 public boolean filterApp(AppEntry info) { 127 if (info == null) return false; 128 return isExtraInfoValid(info.extraInfo) && !isBundled(info) 129 && !isPersistentProcess(info); 130 } 131 132 private boolean isExtraInfoValid(Object extraInfo) { 133 if (extraInfo == null || !(extraInfo instanceof UsageStatsState)) { 134 return false; 135 } 136 137 UsageStatsState state = (UsageStatsState) extraInfo; 138 139 // If we are missing information, let's be conservative and not show it. 140 if (state.daysSinceFirstInstall == UNKNOWN_LAST_USE 141 || state.daysSinceLastUse == UNKNOWN_LAST_USE) { 142 return false; 143 } 144 145 // If the app has never been used, daysSinceLastUse is Long.MAX_VALUE, so the first 146 // install is always the most recent use. 147 long mostRecentUse = Math.min(state.daysSinceFirstInstall, state.daysSinceLastUse); 148 return mostRecentUse >= mUnusedDaysThreshold; 149 } 150 151 private boolean isBundled(AppEntry info) { 152 return (info.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 153 } 154 155 private boolean isPersistentProcess(AppEntry info) { 156 return (info.info.flags & ApplicationInfo.FLAG_PERSISTENT) != 0; 157 } 158 }; 159 160 /** 161 * UsageStatsState contains the days since the last use and first install of a given app. 162 */ 163 public static class UsageStatsState { 164 public long daysSinceLastUse; 165 public long daysSinceFirstInstall; 166 public int userId; 167 168 public UsageStatsState(long daysSinceLastUse, long daysSinceFirstInstall, int userId) { 169 this.daysSinceLastUse = daysSinceLastUse; 170 this.daysSinceFirstInstall = daysSinceFirstInstall; 171 this.userId = userId; 172 } 173 } 174 } 175