Home | History | Annotate | Download | only in deletionhelper
      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.text.format.DateUtils;
     28 import android.util.Log;
     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     public static final long NEVER_USED = Long.MAX_VALUE;
     46     public static final long UNKNOWN_LAST_USE = -1;
     47     public static final long UNUSED_DAYS_DELETION_THRESHOLD = 90;
     48     private static final long DAYS_IN_A_TYPICAL_YEAR = 365;
     49     public static final long MIN_DELETION_THRESHOLD = Long.MIN_VALUE;
     50     public static final int NORMAL_THRESHOLD = 0;
     51     public static final int NO_THRESHOLD = 1;
     52 
     53     private UsageStatsManager mUsageStatsManager;
     54     private PackageManager mPm;
     55     // This clock is used to provide the time. By default, it uses the system clock, but can be
     56     // replaced for test purposes.
     57     protected Clock mClock;
     58 
     59     public AppStateUsageStatsBridge(Context context, ApplicationsState appState,
     60             Callback callback) {
     61         super(appState, callback);
     62         mUsageStatsManager =
     63                 (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
     64         mPm = context.getPackageManager();
     65         mClock = new Clock();
     66     }
     67 
     68     @Override
     69     protected void loadAllExtraInfo() {
     70         ArrayList<AppEntry> apps = mAppSession.getAllApps();
     71         if (apps == null) return;
     72 
     73         final Map<String, UsageStats> map = getAggregatedUsageStats();
     74         for (AppEntry entry : apps) {
     75             UsageStats usageStats = map.get(entry.info.packageName);
     76             entry.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats),
     77                     getDaysSinceInstalled(entry.info.packageName),
     78                     UserHandle.getUserId(entry.info.uid));
     79         }
     80     }
     81 
     82     @Override
     83     protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
     84         Map<String, UsageStats> map = getAggregatedUsageStats();
     85         UsageStats usageStats = map.get(app.info.packageName);
     86         app.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats),
     87                 getDaysSinceInstalled(app.info.packageName),
     88                 UserHandle.getUserId(app.info.uid));
     89     }
     90 
     91     private long getDaysSinceLastUse(UsageStats stats) {
     92         if (stats == null) {
     93             return NEVER_USED;
     94         }
     95         long lastUsed = stats.getLastTimeUsed();
     96         // Sometimes, a usage is recorded without a time and we don't know when the use was.
     97         if (lastUsed <= 0) {
     98             return UNKNOWN_LAST_USE;
     99         }
    100 
    101         // Theoretically, this should be impossible, but UsageStatsService, uh, finds a way.
    102         long days = (TimeUnit.MILLISECONDS.toDays(mClock.getCurrentTime() - lastUsed));
    103         if (days > DAYS_IN_A_TYPICAL_YEAR) {
    104             return NEVER_USED;
    105         }
    106         return days;
    107     }
    108 
    109     private long getDaysSinceInstalled(String packageName) {
    110         PackageInfo pi = null;
    111         try {
    112             pi = mPm.getPackageInfo(packageName, 0);
    113         } catch (PackageManager.NameNotFoundException e) {
    114             Log.e(TAG, packageName + " was not found.");
    115         }
    116 
    117         if (pi == null) {
    118             return UNKNOWN_LAST_USE;
    119         }
    120         return (TimeUnit.MILLISECONDS.toDays(mClock.getCurrentTime() - pi.firstInstallTime));
    121     }
    122 
    123     private Map<String, UsageStats> getAggregatedUsageStats() {
    124         long now = mClock.getCurrentTime();
    125         long startTime = now - DateUtils.YEAR_IN_MILLIS;
    126         return mUsageStatsManager.queryAndAggregateUsageStats(startTime, now);
    127     }
    128 
    129     private static boolean isBundled(AppEntry info) {
    130         return (info.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    131     }
    132 
    133     private static boolean isPersistentProcess(AppEntry info) {
    134         return (info.info.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
    135     }
    136 
    137     private static boolean isExtraInfoValid(Object extraInfo, long unusedDaysThreshold) {
    138         if (extraInfo == null || !(extraInfo instanceof UsageStatsState)) {
    139             return false;
    140         }
    141 
    142         UsageStatsState state = (UsageStatsState) extraInfo;
    143 
    144         // If we are missing information, let's be conservative and not show it.
    145         if (state.daysSinceFirstInstall == UNKNOWN_LAST_USE
    146                 || state.daysSinceLastUse == UNKNOWN_LAST_USE) {
    147             Log.w(TAG, "Missing information. Skipping app");
    148             return false;
    149         }
    150 
    151         // If the app has never been used, daysSinceLastUse is Long.MAX_VALUE, so the first
    152         // install is always the most recent use.
    153         long mostRecentUse = Math.min(state.daysSinceFirstInstall, state.daysSinceLastUse);
    154         return mostRecentUse >= unusedDaysThreshold;
    155     }
    156 
    157     public static final AppFilter FILTER_NO_THRESHOLD =
    158             new AppFilter() {
    159                 @Override
    160                 public void init() {}
    161 
    162                 @Override
    163                 public boolean filterApp(AppEntry info) {
    164                     if (info == null) {
    165                         return false;
    166                     }
    167                     return isExtraInfoValid(info.extraInfo, MIN_DELETION_THRESHOLD)
    168                             && !isBundled(info)
    169                             && !isPersistentProcess(info);
    170                 }
    171             };
    172 
    173     /**
    174      * Filters only non-system apps which haven't been used in the last 60 days. If an app's last
    175      * usage is unknown, it is skipped.
    176      */
    177     public static final AppFilter FILTER_USAGE_STATS =
    178             new AppFilter() {
    179                 private long mUnusedDaysThreshold;
    180 
    181                 @Override
    182                 public void init() {
    183                     mUnusedDaysThreshold =
    184                             SystemProperties.getLong(
    185                                     DEBUG_APP_UNUSED_OVERRIDE, UNUSED_DAYS_DELETION_THRESHOLD);
    186                 }
    187 
    188                 @Override
    189                 public boolean filterApp(AppEntry info) {
    190                     if (info == null) {
    191                         return false;
    192                     }
    193                     return isExtraInfoValid(info.extraInfo, mUnusedDaysThreshold)
    194                             && !isBundled(info)
    195                             && !isPersistentProcess(info);
    196                 }
    197             };
    198 
    199     /**
    200      * UsageStatsState contains the days since the last use and first install of a given app.
    201      */
    202     public static class UsageStatsState {
    203         public long daysSinceLastUse;
    204         public long daysSinceFirstInstall;
    205         public int userId;
    206 
    207         public UsageStatsState(long daysSinceLastUse, long daysSinceFirstInstall, int userId) {
    208             this.daysSinceLastUse = daysSinceLastUse;
    209             this.daysSinceFirstInstall = daysSinceFirstInstall;
    210             this.userId = userId;
    211         }
    212     }
    213 
    214     /**
    215      * Clock provides the current time.
    216      */
    217     static class Clock {
    218         public long getCurrentTime() {
    219             return System.currentTimeMillis();
    220         }
    221     }
    222 }
    223