1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.storagemanager.deletionhelper;
18
19import android.app.usage.UsageStats;
20import android.app.usage.UsageStatsManager;
21import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageInfo;
24import android.os.Looper;
25import com.android.settingslib.applications.ApplicationsState;
26import com.android.storagemanager.testing.TestingConstants;
27import com.android.storagemanager.deletionhelper.AppStateUsageStatsBridge.UsageStatsState;
28import org.junit.Before;
29import org.junit.Test;
30import org.junit.runner.RunWith;
31import org.mockito.ArgumentCaptor;
32import org.mockito.Mock;
33import org.mockito.MockitoAnnotations;
34import org.robolectric.RobolectricTestRunner;
35import org.robolectric.RuntimeEnvironment;
36import org.robolectric.Shadows;
37import org.robolectric.annotation.Config;
38import org.robolectric.res.builder.RobolectricPackageManager;
39import org.robolectric.shadows.ShadowApplication;
40
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.concurrent.TimeUnit;
44
45import static com.google.common.truth.Truth.assertThat;
46import static org.mockito.Matchers.any;
47import static org.mockito.Matchers.anyLong;
48import static org.mockito.Mockito.atLeastOnce;
49import static org.mockito.Mockito.mock;
50import static org.mockito.Mockito.verify;
51import static org.mockito.Mockito.when;
52
53@RunWith(RobolectricTestRunner.class)
54@Config(manifest=TestingConstants.MANIFEST, sdk=23)
55public class AppStateUsageStatsBridgeTest {
56
57    public static final String PACKAGE_SYSTEM = "package.system";
58    private static final long STARTING_TIME = TimeUnit.DAYS.toMillis(1000);
59    private static final String PACKAGE_NAME = "package.mcpackageface";
60    public static final String PACKAGE_CLEARABLE = "package.clearable";
61    public static final String PACKAGE_TOO_NEW_TO_DELETE = "package.tooNewToDelete";
62
63    @Mock private ApplicationsState mState;
64    @Mock private ApplicationsState.Session mSession;
65    @Mock private UsageStatsManager mUsageStatsManager;
66    @Mock private AppStateUsageStatsBridge.Clock mClock;
67    private AppStateUsageStatsBridge mBridge;
68    private ArrayList<ApplicationsState.AppEntry> mApps;
69    private HashMap<String, UsageStats> mUsageStats;
70    private RobolectricPackageManager mPm;
71
72    @Before
73    public void setUp() {
74        MockitoAnnotations.initMocks(this);
75
76        // Set up the application state.
77        when(mState.newSession(any(ApplicationsState.Callbacks.class))).thenReturn(mSession);
78        when(mState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
79
80        // Set up the ApplicationState's session to return our fake list of apps.
81        mApps = new ArrayList<>();
82        when(mSession.getAllApps()).thenReturn(mApps);
83
84        // Set up our mock usage stats service.
85        ShadowApplication app = Shadows.shadowOf(RuntimeEnvironment.application);
86        mPm = RuntimeEnvironment.getRobolectricPackageManager();
87        app.setSystemService(Context.USAGE_STATS_SERVICE, mUsageStatsManager);
88
89        // Set up the AppStateUsageStatsBridge with a fake clock for us to manipulate the time.
90        when(mClock.getCurrentTime()).thenReturn(STARTING_TIME);
91        mBridge = new AppStateUsageStatsBridge(RuntimeEnvironment.application, mState, null);
92        mBridge.mClock = mClock;
93        AppStateUsageStatsBridge.FILTER_USAGE_STATS.init();
94
95        // Set up our fake usage stats.
96        mUsageStats = new HashMap<>();
97        when(mUsageStatsManager.queryAndAggregateUsageStats(anyLong(),
98                anyLong())).thenReturn(mUsageStats);
99    }
100
101    @Test
102    public void test_appInstalledSameDayNeverUsed_isInvalid() {
103        ApplicationsState.AppEntry app =
104                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000));
105
106        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
107        UsageStatsState stats = (UsageStatsState) app.extraInfo;
108
109        assertThat(app.extraInfo).isNotNull();
110        assertThat(stats.daysSinceFirstInstall).isEqualTo(0);
111        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED);
112        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse();
113    }
114
115    @Test
116    public void test_noThresholdFilter_appInstalledSameDayNeverUsed_isValid() {
117        ApplicationsState.AppEntry app =
118                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000));
119
120        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
121        UsageStatsState stats = (UsageStatsState) app.extraInfo;
122
123        assertThat(app.extraInfo).isNotNull();
124        assertThat(stats.daysSinceFirstInstall).isEqualTo(0);
125        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED);
126        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
127    }
128
129    @Test
130    public void test_unusedApp_isValid() {
131        ApplicationsState.AppEntry app =
132                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910));
133
134        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
135        UsageStatsState stats = (UsageStatsState) app.extraInfo;
136
137        assertThat(app.extraInfo).isNotNull();
138        assertThat(stats.daysSinceFirstInstall).isEqualTo(90);
139        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED);
140        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue();
141    }
142
143    @Test
144    public void test_noThresholdFilter_unusedApp_isValid() {
145        ApplicationsState.AppEntry app =
146                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910));
147
148        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
149        UsageStatsState stats = (UsageStatsState) app.extraInfo;
150
151        assertThat(app.extraInfo).isNotNull();
152        assertThat(stats.daysSinceFirstInstall).isEqualTo(90);
153        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED);
154        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
155    }
156
157    @Test
158    public void test_unknownLastUse_isFilteredOut() {
159        ApplicationsState.AppEntry app =
160                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910));
161        registerLastUse(PACKAGE_NAME, -1);
162
163        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
164        UsageStatsState stats = (UsageStatsState) app.extraInfo;
165
166        assertThat(app.extraInfo).isNotNull();
167        assertThat(stats.daysSinceFirstInstall).isEqualTo(90);
168        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.UNKNOWN_LAST_USE);
169        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse();
170    }
171
172    @Test
173    public void test_noThresholdFilter_unknownLastUse_isFilteredOut() {
174        ApplicationsState.AppEntry app =
175                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910));
176        registerLastUse(PACKAGE_NAME, -1);
177
178        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
179        UsageStatsState stats = (UsageStatsState) app.extraInfo;
180
181        assertThat(app.extraInfo).isNotNull();
182        assertThat(stats.daysSinceFirstInstall).isEqualTo(90);
183        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.UNKNOWN_LAST_USE);
184        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
185    }
186
187    @Test
188    public void test_oldAppRecentlyUsed_isNotValid() {
189        ApplicationsState.AppEntry app =
190                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
191        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(999));
192
193        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
194        UsageStatsState stats = (UsageStatsState) app.extraInfo;
195
196        assertThat(app.extraInfo).isNotNull();
197        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
198        assertThat(stats.daysSinceLastUse).isEqualTo(1);
199        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse();
200    }
201
202    @Test
203    public void test_noThresholdFilter_oldAppRecentlyUsed_isValid() {
204        ApplicationsState.AppEntry app =
205                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
206        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(999));
207
208        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
209        UsageStatsState stats = (UsageStatsState) app.extraInfo;
210
211        assertThat(app.extraInfo).isNotNull();
212        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
213        assertThat(stats.daysSinceLastUse).isEqualTo(1);
214        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
215    }
216
217    @Test
218    public void test_oldUnusedApp_isValid() {
219        ApplicationsState.AppEntry app =
220                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
221        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(801));
222
223        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
224        UsageStatsState stats = (UsageStatsState) app.extraInfo;
225
226        assertThat(app.extraInfo).isNotNull();
227        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
228        assertThat(stats.daysSinceLastUse).isEqualTo(199);
229        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue();
230    }
231
232    @Test
233    public void test_noThresholdFilter_oldUnusedApp_isValid() {
234        ApplicationsState.AppEntry app =
235                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
236        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(801));
237
238        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
239        UsageStatsState stats = (UsageStatsState) app.extraInfo;
240
241        assertThat(app.extraInfo).isNotNull();
242        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
243        assertThat(stats.daysSinceLastUse).isEqualTo(199);
244        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
245    }
246
247    @Test
248    public void test_systemApps_areInvalid() {
249        ApplicationsState.AppEntry app =
250                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
251        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
252        app.info.flags = ApplicationInfo.FLAG_SYSTEM;
253
254        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
255        UsageStatsState stats = (UsageStatsState) app.extraInfo;
256
257        assertThat(app.extraInfo).isNotNull();
258        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
259        assertThat(stats.daysSinceLastUse).isEqualTo(200);
260        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse();
261    }
262
263    @Test
264    public void test_noThresholdFilter_systemApps_areInvalid() {
265        ApplicationsState.AppEntry app =
266                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
267        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
268        app.info.flags = ApplicationInfo.FLAG_SYSTEM;
269
270        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
271        UsageStatsState stats = (UsageStatsState) app.extraInfo;
272
273        assertThat(app.extraInfo).isNotNull();
274        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
275        assertThat(stats.daysSinceLastUse).isEqualTo(200);
276        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
277    }
278
279    @Test
280    public void test_persistentProcessApps_areInvalid() {
281        ApplicationsState.AppEntry app =
282                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
283        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
284        app.info.flags = ApplicationInfo.FLAG_PERSISTENT;
285
286        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
287        UsageStatsState stats = (UsageStatsState) app.extraInfo;
288
289        assertThat(app.extraInfo).isNotNull();
290        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
291        assertThat(stats.daysSinceLastUse).isEqualTo(200);
292        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse();
293    }
294
295    @Test
296    public void test_noThresholdFilter_persistentProcessApps_areInvalid() {
297        ApplicationsState.AppEntry app =
298                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
299        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
300        app.info.flags = ApplicationInfo.FLAG_PERSISTENT;
301
302        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
303        UsageStatsState stats = (UsageStatsState) app.extraInfo;
304
305        assertThat(app.extraInfo).isNotNull();
306        assertThat(stats.daysSinceFirstInstall).isEqualTo(200);
307        assertThat(stats.daysSinceLastUse).isEqualTo(200);
308        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
309    }
310
311    @Test
312    public void test_multipleApps_processCorrectly() {
313        ApplicationsState.AppEntry clearable =
314                addPackageToPackageManager(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
315        registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
316        mApps.add(clearable);
317        ApplicationsState.AppEntry tooNewtoDelete =
318                addPackageToPackageManager(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
319        registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
320        mApps.add(tooNewtoDelete);
321        ApplicationsState.AppEntry systemApp =
322                addPackageToPackageManager(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
323        registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
324        systemApp.info.flags = ApplicationInfo.FLAG_SYSTEM;
325        mApps.add(systemApp);
326        ApplicationsState.AppEntry persistentApp =
327                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
328        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
329        persistentApp.info.flags = ApplicationInfo.FLAG_PERSISTENT;
330        mApps.add(persistentApp);
331
332        mBridge.loadAllExtraInfo();
333
334        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(clearable)).isTrue();
335        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(tooNewtoDelete)).isFalse();
336        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(systemApp)).isFalse();
337        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(persistentApp)).isFalse();
338    }
339
340    @Test
341    public void test_noThresholdFilter_ignoresUsageForFiltering() {
342        ApplicationsState.AppEntry clearable =
343                addPackageToPackageManager(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
344        registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
345        mApps.add(clearable);
346        ApplicationsState.AppEntry tooNewtoDelete =
347                addPackageToPackageManager(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
348        registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
349        mApps.add(tooNewtoDelete);
350        ApplicationsState.AppEntry systemApp =
351                addPackageToPackageManager(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
352        registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
353        systemApp.info.flags = ApplicationInfo.FLAG_SYSTEM;
354        mApps.add(systemApp);
355        ApplicationsState.AppEntry persistentApp =
356                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
357        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
358        persistentApp.info.flags = ApplicationInfo.FLAG_PERSISTENT;
359        mApps.add(persistentApp);
360
361        mBridge.loadAllExtraInfo();
362
363        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(clearable)).isTrue();
364        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(tooNewtoDelete)).isTrue();
365        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(systemApp)).isFalse();
366        assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(persistentApp)).isFalse();
367    }
368
369    @Test
370    public void testAppUsedOverOneYearAgoIsValid() {
371        ApplicationsState.AppEntry app =
372                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(600));
373        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000 - 366));
374
375        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
376        UsageStatsState stats = (UsageStatsState) app.extraInfo;
377
378        assertThat(app.extraInfo).isNotNull();
379        assertThat(stats.daysSinceFirstInstall).isEqualTo(400);
380        assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED);
381        assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue();
382    }
383
384    @Test
385    public void testStartEndIsBeforeEndTimeInQuery() {
386        ApplicationsState.AppEntry app =
387                addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(600));
388        registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000 - 366));
389        ArgumentCaptor<Long> startTimeCaptor = ArgumentCaptor.forClass(Long.class);
390        ArgumentCaptor<Long> endTimeCaptor = ArgumentCaptor.forClass(Long.class);
391
392        mBridge.updateExtraInfo(app, PACKAGE_NAME, 0);
393
394        verify(mUsageStatsManager, atLeastOnce())
395                .queryAndAggregateUsageStats(startTimeCaptor.capture(), endTimeCaptor.capture());
396        assertThat(startTimeCaptor.getValue()).isLessThan(endTimeCaptor.getValue());
397    }
398
399    private ApplicationsState.AppEntry addPackageToPackageManager(String packageName,
400            long installTime) {
401        PackageInfo testPackage = new PackageInfo();
402        testPackage.packageName = packageName;
403        testPackage.firstInstallTime = installTime;
404        mPm.addPackage(testPackage);
405        ApplicationsState.AppEntry app = mock(ApplicationsState.AppEntry.class);
406        ApplicationInfo info = mock(ApplicationInfo.class);
407        info.packageName = packageName;
408        app.info = info;
409        return app;
410    }
411
412    private void registerLastUse(String packageName, long time) {
413        UsageStats usageStats = mock(UsageStats.class);
414        when(usageStats.getPackageName()).thenReturn(packageName);
415        when(usageStats.getLastTimeUsed()).thenReturn(time);
416        mUsageStats.put(packageName, usageStats);
417    }
418
419    private class FakeClock extends AppStateUsageStatsBridge.Clock {
420        public long time;
421
422        @Override
423        public long getCurrentTime() {
424            return time;
425        }
426    }
427}
428