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.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