AppStateUsageStatsBridge.java revision da3079c55551e61a40a0f434dc8a14327cfec7e2
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 */
16package com.android.storagemanager.deletionhelper;
17
18import android.app.usage.UsageStats;
19import android.app.usage.UsageStatsManager;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.os.SystemProperties;
26import android.os.UserHandle;
27import android.text.format.DateUtils;
28import android.util.Log;
29import com.android.storagemanager.deletionhelper.AppStateBaseBridge.Callback;
30import com.android.settingslib.applications.ApplicationsState;
31import com.android.settingslib.applications.ApplicationsState.AppEntry;
32import com.android.settingslib.applications.ApplicationsState.AppFilter;
33
34import java.util.ArrayList;
35import java.util.Map;
36import java.util.concurrent.TimeUnit;
37
38/**
39 * Connects data from the UsageStatsManager to the ApplicationsState.
40 */
41public 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