| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package com.android.storagemanager.deletionhelper; |
| |
| import android.app.usage.UsageStats; |
| import android.app.usage.UsageStatsManager; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.os.Looper; |
| import com.android.settingslib.applications.ApplicationsState; |
| import com.android.storagemanager.deletionhelper.AppStateUsageStatsBridge.UsageStatsState; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.robolectric.RobolectricTestRunner; |
| import org.robolectric.RuntimeEnvironment; |
| import org.robolectric.Shadows; |
| import org.robolectric.shadows.ShadowApplication; |
| import org.robolectric.shadows.ShadowPackageManager; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.concurrent.TimeUnit; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| import static org.robolectric.Shadows.shadowOf; |
| |
| @RunWith(RobolectricTestRunner.class) |
| public class AppStateUsageStatsBridgeTest { |
| |
| private static final String PACKAGE_SYSTEM = "package.system"; |
| private static final long STARTING_TIME = TimeUnit.DAYS.toMillis(1000); |
| private static final String PACKAGE_NAME = "package.mcpackageface"; |
| private static final String PACKAGE_CLEARABLE = "package.clearable"; |
| private static final String PACKAGE_TOO_NEW_TO_DELETE = "package.tooNewToDelete"; |
| |
| @Mock private ApplicationsState mState; |
| @Mock private ApplicationsState.Session mSession; |
| @Mock private UsageStatsManager mUsageStatsManager; |
| @Mock private AppStateUsageStatsBridge.Clock mClock; |
| private AppStateUsageStatsBridge mBridge; |
| private ArrayList<ApplicationsState.AppEntry> mApps; |
| private HashMap<String, UsageStats> mUsageStats; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| |
| // Set up the application state. |
| when(mState.newSession(any(ApplicationsState.Callbacks.class))).thenReturn(mSession); |
| when(mState.getBackgroundLooper()).thenReturn(Looper.getMainLooper()); |
| |
| // Set up the ApplicationState's session to return our fake list of apps. |
| mApps = new ArrayList<>(); |
| when(mSession.getAllApps()).thenReturn(mApps); |
| |
| // Set up our mock usage stats service. |
| ShadowApplication app = Shadows.shadowOf(RuntimeEnvironment.application); |
| app.setSystemService(Context.USAGE_STATS_SERVICE, mUsageStatsManager); |
| |
| // Set up the AppStateUsageStatsBridge with a fake clock for us to manipulate the time. |
| when(mClock.getCurrentTime()).thenReturn(STARTING_TIME); |
| mBridge = new AppStateUsageStatsBridge(RuntimeEnvironment.application, mState, null); |
| mBridge.mClock = mClock; |
| AppStateUsageStatsBridge.FILTER_USAGE_STATS.init(); |
| |
| // Set up our fake usage stats. |
| mUsageStats = new HashMap<>(); |
| when(mUsageStatsManager.queryAndAggregateUsageStats(anyLong(), |
| anyLong())).thenReturn(mUsageStats); |
| } |
| |
| @Test |
| public void test_appInstalledSameDayNeverUsed_isInvalid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(0); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_appInstalledSameDayNeverUsed_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(0); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_unusedApp_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(90); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_unusedApp_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(90); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_unknownLastUse_isFilteredOut() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910)); |
| registerLastUse(PACKAGE_NAME, -1); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(90); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.UNKNOWN_LAST_USE); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_unknownLastUse_isFilteredOut() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(910)); |
| registerLastUse(PACKAGE_NAME, -1); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(90); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.UNKNOWN_LAST_USE); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_oldAppRecentlyUsed_isNotValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(999)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(1); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_oldAppRecentlyUsed_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(999)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(1); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_oldUnusedApp_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(801)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(199); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_oldUnusedApp_isValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(801)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(199); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void test_systemApps_areInvalid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| app.info.flags = ApplicationInfo.FLAG_SYSTEM; |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(200); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_systemApps_areInvalid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| app.info.flags = ApplicationInfo.FLAG_SYSTEM; |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(200); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_persistentProcessApps_areInvalid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| app.info.flags = ApplicationInfo.FLAG_PERSISTENT; |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(200); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_persistentProcessApps_areInvalid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| app.info.flags = ApplicationInfo.FLAG_PERSISTENT; |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(200); |
| assertThat(stats.daysSinceLastUse).isEqualTo(200); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(app)).isFalse(); |
| } |
| |
| @Test |
| public void test_multipleApps_processCorrectly() { |
| ApplicationsState.AppEntry clearable = |
| addPackageToPackageManager(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800)); |
| mApps.add(clearable); |
| ApplicationsState.AppEntry tooNewtoDelete = |
| addPackageToPackageManager(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000)); |
| registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000)); |
| mApps.add(tooNewtoDelete); |
| ApplicationsState.AppEntry systemApp = |
| addPackageToPackageManager(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800)); |
| systemApp.info.flags = ApplicationInfo.FLAG_SYSTEM; |
| mApps.add(systemApp); |
| ApplicationsState.AppEntry persistentApp = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| persistentApp.info.flags = ApplicationInfo.FLAG_PERSISTENT; |
| mApps.add(persistentApp); |
| |
| mBridge.loadAllExtraInfo(); |
| |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(clearable)).isTrue(); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(tooNewtoDelete)).isFalse(); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(systemApp)).isFalse(); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(persistentApp)).isFalse(); |
| } |
| |
| @Test |
| public void test_noThresholdFilter_ignoresUsageForFiltering() { |
| ApplicationsState.AppEntry clearable = |
| addPackageToPackageManager(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800)); |
| mApps.add(clearable); |
| ApplicationsState.AppEntry tooNewtoDelete = |
| addPackageToPackageManager(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000)); |
| registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000)); |
| mApps.add(tooNewtoDelete); |
| ApplicationsState.AppEntry systemApp = |
| addPackageToPackageManager(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800)); |
| systemApp.info.flags = ApplicationInfo.FLAG_SYSTEM; |
| mApps.add(systemApp); |
| ApplicationsState.AppEntry persistentApp = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800)); |
| persistentApp.info.flags = ApplicationInfo.FLAG_PERSISTENT; |
| mApps.add(persistentApp); |
| |
| mBridge.loadAllExtraInfo(); |
| |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(clearable)).isTrue(); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(tooNewtoDelete)).isTrue(); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(systemApp)).isFalse(); |
| assertThat(AppStateUsageStatsBridge.FILTER_NO_THRESHOLD.filterApp(persistentApp)).isFalse(); |
| } |
| |
| @Test |
| public void testAppUsedOverOneYearAgoIsValid() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(600)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000 - 366)); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| UsageStatsState stats = (UsageStatsState) app.extraInfo; |
| |
| assertThat(app.extraInfo).isNotNull(); |
| assertThat(stats.daysSinceFirstInstall).isEqualTo(400); |
| assertThat(stats.daysSinceLastUse).isEqualTo(AppStateUsageStatsBridge.NEVER_USED); |
| assertThat(AppStateUsageStatsBridge.FILTER_USAGE_STATS.filterApp(app)).isTrue(); |
| } |
| |
| @Test |
| public void testStartEndIsBeforeEndTimeInQuery() { |
| ApplicationsState.AppEntry app = |
| addPackageToPackageManager(PACKAGE_NAME, TimeUnit.DAYS.toMillis(600)); |
| registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(1000 - 366)); |
| ArgumentCaptor<Long> startTimeCaptor = ArgumentCaptor.forClass(Long.class); |
| ArgumentCaptor<Long> endTimeCaptor = ArgumentCaptor.forClass(Long.class); |
| |
| mBridge.updateExtraInfo(app, PACKAGE_NAME, 0); |
| |
| verify(mUsageStatsManager, atLeastOnce()) |
| .queryAndAggregateUsageStats(startTimeCaptor.capture(), endTimeCaptor.capture()); |
| assertThat(startTimeCaptor.getValue()).isLessThan(endTimeCaptor.getValue()); |
| } |
| |
| private ApplicationsState.AppEntry addPackageToPackageManager(String packageName, |
| long installTime) { |
| PackageInfo testPackage = new PackageInfo(); |
| testPackage.packageName = packageName; |
| testPackage.firstInstallTime = installTime; |
| |
| ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager()); |
| spm.addPackage(testPackage); |
| |
| ApplicationsState.AppEntry app = mock(ApplicationsState.AppEntry.class); |
| ApplicationInfo info = mock(ApplicationInfo.class); |
| info.packageName = packageName; |
| app.info = info; |
| return app; |
| } |
| |
| private void registerLastUse(String packageName, long time) { |
| UsageStats usageStats = mock(UsageStats.class); |
| when(usageStats.getPackageName()).thenReturn(packageName); |
| when(usageStats.getLastTimeUsed()).thenReturn(time); |
| mUsageStats.put(packageName, usageStats); |
| } |
| } |