blob: 4fbdf28d4216c14d3f8a85f49d986c676673f15c [file] [log] [blame]
/*
* 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.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.storage.VolumeInfo;
import android.text.TextUtils;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
import com.android.storagemanager.deletionhelper.AppsAsyncLoader.PackageInfo;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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 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.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class AppsAsyncLoaderTest {
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";
private static final String PACKAGE_DEFAULT_LAUNCHER = "package.launcherface";
@Mock private UsageStatsManager mUsageStatsManager;
@Mock private StorageStatsSource mStorageStatsSource;
@Mock private AppsAsyncLoader.Clock mClock;
@Mock private PackageManager mPackageManager;
@Mock private AppStorageStats mAppStorageStats;
private AppsAsyncLoader mLoader;
private HashMap<String, UsageStats> mUsageStats;
private ArrayList<ApplicationInfo> mInfo;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// Set up our mock usage app service.
ShadowApplication app = Shadows.shadowOf(RuntimeEnvironment.application);
app.setSystemService(Context.USAGE_STATS_SERVICE, mUsageStatsManager);
// Initialize filters and loader with mock objects
AppsAsyncLoader.FILTER_NO_THRESHOLD.init();
AppsAsyncLoader.FILTER_USAGE_STATS.init();
// Set up the AppsAsyncLoader with a fake clock for us to manipulate the time.
when(mClock.getCurrentTime()).thenReturn(STARTING_TIME);
// Set up the loader to return our fake list of apps.
mInfo = new ArrayList<>();
when(mPackageManager.getInstalledApplicationsAsUser(anyInt(), anyInt())).thenReturn(mInfo);
when(mPackageManager.getHomeActivities(any(List.class)))
.thenReturn(new ComponentName(PACKAGE_DEFAULT_LAUNCHER, ""));
AppsAsyncLoader.FILTER_USAGE_STATS.init();
// Set up our fake usage app.
mUsageStats = new HashMap<>();
when(mUsageStatsManager.queryAndAggregateUsageStats(anyLong(), anyLong()))
.thenReturn(mUsageStats);
when(mStorageStatsSource.getStatsForUid(any(), anyInt())).thenReturn(mAppStorageStats);
mLoader =
new AppsAsyncLoader.Builder(RuntimeEnvironment.application)
.setUid(0)
.setUuid(VolumeInfo.ID_PRIVATE_INTERNAL)
.setStorageStatsSource(mStorageStatsSource)
.setPackageManager(mPackageManager)
.setUsageStatsManager(mUsageStatsManager)
.setFilter(AppsAsyncLoader.FILTER_NO_THRESHOLD)
.build();
mLoader.mClock = mClock;
}
@Test
public void test_appInstalledSameDayNeverUsed_isInvalid() {
AppsAsyncLoader.PackageInfo app =
createPackage(PACKAGE_NAME, AppsAsyncLoader.NEVER_USED, 0);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isFalse();
}
@Test
public void test_noThresholdFilter_appInstalledSameDayNeverUsed_isValid() {
AppsAsyncLoader.PackageInfo app =
createPackage(PACKAGE_NAME, AppsAsyncLoader.NEVER_USED, 0);
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
}
@Test
public void test_unusedApp_isValid() {
AppsAsyncLoader.PackageInfo app =
createPackage(PACKAGE_NAME, AppsAsyncLoader.NEVER_USED, 90);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isTrue();
}
@Test
public void test_noThresholdFilter_unusedApp_isValid() {
AppsAsyncLoader.PackageInfo app =
createPackage(PACKAGE_NAME, AppsAsyncLoader.NEVER_USED, 90);
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
}
@Test
public void test_unknownLastUse_isFilteredOut() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, -1, 90);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isFalse();
}
@Test
public void test_noThresholdFilter_unknownLastUse_isFilteredOut() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, -1, 90);
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
}
@Test
public void test_oldAppRecentlyUsed_isNotValid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 1, 200);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isFalse();
}
@Test
public void test_noThresholdFilter_oldAppRecentlyUsed_isValid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 1, 200);
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
}
@Test
public void test_oldUnusedApp_isValid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 199, 200);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isTrue();
}
@Test
public void test_noThresholdFilter_oldUnusedApp_isValid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 199, 200);
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isTrue();
}
@Test
public void test_systemApps_areInvalid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 200, 200);
app.flags = ApplicationInfo.FLAG_SYSTEM;
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isFalse();
}
@Test
public void test_noThresholdFilter_systemApps_areInvalid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 200, 200);
app.flags = ApplicationInfo.FLAG_SYSTEM;
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
}
@Test
public void test_persistentProcessApps_areInvalid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 200, 200);
app.flags = ApplicationInfo.FLAG_PERSISTENT;
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isFalse();
}
@Test
public void test_noThresholdFilter_persistentProcessApps_areInvalid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 200, 200);
app.flags = ApplicationInfo.FLAG_PERSISTENT;
assertThat(AppsAsyncLoader.FILTER_NO_THRESHOLD.filterApp(app)).isFalse();
}
@Test
public void test_multipleApps_processCorrectly() {
mLoader.mFilter = AppsAsyncLoader.FILTER_USAGE_STATS;
mLoader.mFilter.init();
AppsAsyncLoader.PackageInfo clearable =
createPackage(
PACKAGE_CLEARABLE,
TimeUnit.DAYS.toMillis(800),
TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
registerApp(clearable, 0, TimeUnit.DAYS.toMillis(800));
AppsAsyncLoader.PackageInfo tooNewtoDelete =
createPackage(
PACKAGE_TOO_NEW_TO_DELETE,
TimeUnit.DAYS.toMillis(1000),
TimeUnit.DAYS.toMillis(1000));
registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
registerApp(tooNewtoDelete, 1, TimeUnit.DAYS.toMillis(1000));
AppsAsyncLoader.PackageInfo systemApp =
createPackage(
PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800), TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
systemApp.flags = ApplicationInfo.FLAG_SYSTEM;
registerApp(systemApp, 2, TimeUnit.DAYS.toMillis(800));
AppsAsyncLoader.PackageInfo persistentApp =
createPackage(
PACKAGE_NAME, TimeUnit.DAYS.toMillis(800), TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
persistentApp.flags = ApplicationInfo.FLAG_PERSISTENT;
registerApp(persistentApp, 3, TimeUnit.DAYS.toMillis(800));
List<AppsAsyncLoader.PackageInfo> infos = mLoader.loadInBackground();
assertThat(containsPackage(infos, PACKAGE_CLEARABLE)).isTrue();
assertThat(containsPackage(infos, PACKAGE_TOO_NEW_TO_DELETE)).isFalse();
assertThat(containsPackage(infos, PACKAGE_NAME)).isFalse();
assertThat(containsPackage(infos, PACKAGE_SYSTEM)).isFalse();
}
@Test
public void test_noThresholdFilter_ignoresUsageForFiltering() {
mLoader.mFilter = AppsAsyncLoader.FILTER_NO_THRESHOLD;
mLoader.mFilter.init();
AppsAsyncLoader.PackageInfo clearable =
createPackage(
PACKAGE_CLEARABLE,
TimeUnit.DAYS.toMillis(800),
TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_CLEARABLE, TimeUnit.DAYS.toMillis(800));
registerApp(clearable, 0, TimeUnit.DAYS.toMillis(800));
AppsAsyncLoader.PackageInfo tooNewtoDelete =
createPackage(
PACKAGE_TOO_NEW_TO_DELETE,
TimeUnit.DAYS.toMillis(1000),
TimeUnit.DAYS.toMillis(1000));
registerLastUse(PACKAGE_TOO_NEW_TO_DELETE, TimeUnit.DAYS.toMillis(1000));
registerApp(tooNewtoDelete, 1, TimeUnit.DAYS.toMillis(1000));
AppsAsyncLoader.PackageInfo systemApp =
createPackage(
PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800), TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_SYSTEM, TimeUnit.DAYS.toMillis(800));
systemApp.flags = ApplicationInfo.FLAG_SYSTEM;
registerApp(systemApp, 2, TimeUnit.DAYS.toMillis(800));
AppsAsyncLoader.PackageInfo persistentApp =
createPackage(
PACKAGE_NAME, TimeUnit.DAYS.toMillis(800), TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_NAME, TimeUnit.DAYS.toMillis(800));
persistentApp.flags = ApplicationInfo.FLAG_PERSISTENT;
registerApp(persistentApp, 3, TimeUnit.DAYS.toMillis(800));
List<AppsAsyncLoader.PackageInfo> infos = mLoader.loadInBackground();
assertThat(containsPackage(infos, PACKAGE_CLEARABLE)).isTrue();
assertThat(containsPackage(infos, PACKAGE_TOO_NEW_TO_DELETE)).isTrue();
assertThat(containsPackage(infos, PACKAGE_NAME)).isFalse();
assertThat(containsPackage(infos, PACKAGE_SYSTEM)).isFalse();
}
@Test
public void testAppUsedOverOneYearAgoIsValid() {
AppsAsyncLoader.PackageInfo app = createPackage(PACKAGE_NAME, 1000 - 366, 400);
assertThat(AppsAsyncLoader.FILTER_USAGE_STATS.filterApp(app)).isTrue();
}
@Test
public void test_getGreaterUsageStats_primaryIsNull() {
UsageStats secondary = mock(UsageStats.class);
when(secondary.getLastTimeUsed()).thenReturn(1000L);
assertThat(mLoader.getGreaterUsageStats(PACKAGE_NAME, null, secondary))
.isEqualTo(secondary);
}
@Test
public void test_getGreaterUsageStats_secondaryIsNull() {
UsageStats primary = mock(UsageStats.class);
when(primary.getLastTimeUsed()).thenReturn(1000L);
assertThat(mLoader.getGreaterUsageStats(PACKAGE_NAME, primary, null)).isEqualTo(primary);
}
@Test
public void test_getGreaterUsageStats_primaryIsGreater() {
UsageStats primary = mock(UsageStats.class);
when(primary.getLastTimeUsed()).thenReturn(1000L);
UsageStats secondary = mock(UsageStats.class);
when(secondary.getLastTimeUsed()).thenReturn(900L);
assertThat(mLoader.getGreaterUsageStats(PACKAGE_NAME, primary, secondary))
.isEqualTo(primary);
}
@Test
public void test_getGreaterUsageStats_secondaryIsGreater() {
UsageStats primary = mock(UsageStats.class);
when(primary.getLastTimeUsed()).thenReturn(900L);
UsageStats secondary = mock(UsageStats.class);
when(secondary.getLastTimeUsed()).thenReturn(1000L);
assertThat(mLoader.getGreaterUsageStats(PACKAGE_NAME, primary, secondary))
.isEqualTo(secondary);
}
@Test
public void test_defaultLauncherDisallowedFromDeletion() {
mLoader.mFilter = AppsAsyncLoader.FILTER_USAGE_STATS;
mLoader.mFilter.init();
AppsAsyncLoader.PackageInfo defaultLauncher =
createPackage(
PACKAGE_DEFAULT_LAUNCHER,
TimeUnit.DAYS.toMillis(800),
TimeUnit.DAYS.toMillis(800));
registerLastUse(PACKAGE_DEFAULT_LAUNCHER, TimeUnit.DAYS.toMillis(800));
registerApp(defaultLauncher, 0, TimeUnit.DAYS.toMillis(800));
List<AppsAsyncLoader.PackageInfo> infos = mLoader.loadInBackground();
assertThat(containsPackage(infos, PACKAGE_DEFAULT_LAUNCHER)).isFalse();
}
private AppsAsyncLoader.PackageInfo createPackage(
String packageName, long lastUse, long installTime) {
AppsAsyncLoader.PackageInfo app =
new AppsAsyncLoader.PackageInfo.Builder()
.setDaysSinceLastUse(lastUse)
.setDaysSinceFirstInstall(installTime)
.setPackageName(packageName)
.setLabel("")
.build();
app.packageName = packageName;
app.label = packageName;
return app;
}
private void registerApp(AppsAsyncLoader.PackageInfo info, int uid, long installed) {
ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
applicationInfo.uid = uid;
applicationInfo.packageName = info.packageName;
applicationInfo.flags = info.flags;
mInfo.add(applicationInfo);
android.content.pm.PackageInfo packageInfo = mock(android.content.pm.PackageInfo.class);
packageInfo.firstInstallTime = installed;
try {
when(mPackageManager.getPackageInfo(eq(info.packageName), anyInt()))
.thenReturn(packageInfo);
when(applicationInfo.loadLabel(eq(mPackageManager)))
.thenReturn(applicationInfo.packageName);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
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);
}
private boolean containsPackage(List<PackageInfo> infos, String expectedPackage) {
for (PackageInfo info : infos) {
if (TextUtils.equals(info.packageName, expectedPackage)) {
return true;
}
}
return false;
}
}