fix: 首次提交

This commit is contained in:
2024-12-09 11:25:23 +08:00
parent d0c01071e9
commit 2c2109a5f3
4741 changed files with 290641 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2022 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.settingslib.applications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import android.graphics.drawable.Drawable;
import org.junit.After;
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;
@RunWith(RobolectricTestRunner.class)
public class AppIconCacheManagerTest {
private static final String APP_PACKAGE_NAME = "com.test.app";
private static final String APP_PACKAGE_NAME1 = "com.test.app1";
private static final String APP_PACKAGE_NAME2 = "com.test.app2";
private static final String APP_PACKAGE_NAME3 = "com.test.app3";
private static final int APP_UID = 9999;
@Mock
private Drawable mIcon;
@Mock
private Drawable mIcon1;
@Mock
private Drawable mIcon2;
@Mock
private Drawable mIcon3;
private AppIconCacheManager mAppIconCacheManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mAppIconCacheManager = AppIconCacheManager.getInstance();
doReturn(10).when(mIcon).getIntrinsicHeight();
doReturn(10).when(mIcon).getIntrinsicWidth();
doReturn(mIcon).when(mIcon).mutate();
// Algorithm for trim memory test:
// The real maxsize is defined by AppIconCacheManager.MAX_CACHE_SIZE_IN_KB, and the size
// of each element is calculated as following:
// n * n * 4 / 1024
// In the testcase, we want to mock the maxsize of LruCache is 3, so the formula calculating
// the size of each element will be like:
// n * n * 4 / 1024 = maxsize / 3
// Thus, n = square_root(maxsize / 3 * 1024 / 4), which can be used as an icon size.
final int iconSize =
(int) Math.sqrt(AppIconCacheManager.MAX_CACHE_SIZE_IN_KB / 3f * 1024f / 4f);
doReturn(iconSize).when(mIcon1).getIntrinsicHeight();
doReturn(iconSize).when(mIcon1).getIntrinsicWidth();
doReturn(mIcon1).when(mIcon1).mutate();
doReturn(iconSize).when(mIcon2).getIntrinsicHeight();
doReturn(iconSize).when(mIcon2).getIntrinsicWidth();
doReturn(mIcon2).when(mIcon2).mutate();
doReturn(iconSize).when(mIcon3).getIntrinsicHeight();
doReturn(iconSize).when(mIcon3).getIntrinsicWidth();
doReturn(mIcon3).when(mIcon3).mutate();
}
@After
public void tearDown() {
AppIconCacheManager.release();
}
@Test
public void get_invalidPackageOrUid_shouldReturnNull() {
assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
}
@Test
public void put_invalidPackageOrUid_shouldNotCrash() {
mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
// no crash
}
@Test
public void put_invalidIcon_shouldNotCacheIcon() {
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
}
@Test
public void put_invalidIconSize_shouldNotCacheIcon() {
doReturn(-1).when(mIcon).getIntrinsicHeight();
doReturn(-1).when(mIcon).getIntrinsicWidth();
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
}
@Test
public void put_shouldCacheIcon() {
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
}
@Test
public void release_noInstance_shouldNotCrash() {
mAppIconCacheManager = null;
AppIconCacheManager.release();
// no crash
}
@Test
public void release_existInstance_shouldClearCache() {
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
AppIconCacheManager.release();
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
}
@Test
public void trimMemory_levelSatisfied_shouldNotCacheIcon() {
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
// Expected to trim size to 0
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
mAppIconCacheManager.trimMemory(level);
// None of the elements should be cached
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNull();
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
}
@Test
public void trimMemory_levelSatisfied_shouldCacheAtLeastHalf() {
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
// Get the last element
mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID);
// Expected to trim size to half of it, which is int( 3 / 2 ) = 1
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
mAppIconCacheManager.trimMemory(level);
// There should be only one cached element, which is the last recently used one
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNotNull();
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2022 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.settingslib.applications;
import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.Utils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
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.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowPackageManager;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
public class AppUtilsTest {
private static final String APP_PACKAGE_NAME = "com.test.app";
private static final int APP_UID = 9999;
@Mock
private Drawable mIcon;
private Context mContext;
private AppIconCacheManager mAppIconCacheManager;
private ApplicationInfo mAppInfo;
private ApplicationsState.AppEntry mAppEntry;
private ArrayList<ApplicationsState.AppEntry> mAppEntries;
private ShadowPackageManager mShadowPackageManager;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mAppIconCacheManager = AppIconCacheManager.getInstance();
mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
doReturn(mIcon).when(mIcon).mutate();
mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
}
@After
public void tearDown() {
AppIconCacheManager.release();
}
@Test
public void getIcon_nullAppEntry_shouldReturnNull() {
assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
}
@Test
@Config(shadows = ShadowUtils.class)
public void getIcon_noCachedIcon_shouldNotReturnNull() {
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
}
@Test
public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
}
@Test
public void getIconFromCache_nullAppEntry_shouldReturnNull() {
assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
}
@Test
public void getIconFromCache_shouldReturnCachedIcon() {
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
}
@Test
public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
// no crash
}
@Test
public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
}
@Test
@Config(shadows = ShadowUtils.class)
public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
TimeUnit.SECONDS.sleep(1);
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
}
@Test
public void isAppInstalled_noAppEntry_shouldReturnFalse() {
assertThat(AppUtils.isAppInstalled(null)).isFalse();
}
@Test
public void isAppInstalled_hasAppEntryWithInstalledFlag_shouldReturnTrue() {
final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
appEntry.info = new ApplicationInfo();
appEntry.info.flags = ApplicationInfo.FLAG_INSTALLED;
assertThat(AppUtils.isAppInstalled(appEntry)).isTrue();
}
@Test
public void isAppInstalled_hasAppEntryWithoutInstalledFlag_shouldReturnFalse() {
final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
appEntry.info = new ApplicationInfo();
assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
}
@Test
public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = APP_PACKAGE_NAME;
packageInfo.setApexPackageName("com.test.apex.package");
mShadowPackageManager.installPackage(packageInfo);
assertThat(
AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
}
@Test
public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = APP_PACKAGE_NAME;
packageInfo.applicationInfo = applicationInfo;
mShadowPackageManager.installPackage(packageInfo);
assertThat(
AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
}
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
appEntry.label = "label";
appEntry.mounted = true;
final File apkFile = mock(File.class);
doReturn(true).when(apkFile).exists();
try {
Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
field.setAccessible(true);
field.set(appEntry, apkFile);
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Not able to mock apkFile: " + e);
}
return appEntry;
}
private ApplicationInfo createApplicationInfo(String packageName, int uid) {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.sourceDir = "appPath";
appInfo.packageName = packageName;
appInfo.uid = uid;
return appInfo;
}
@Implements(Utils.class)
private static class ShadowUtils {
@Implementation
public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
final Drawable icon = mock(Drawable.class);
doReturn(10).when(icon).getIntrinsicHeight();
doReturn(10).when(icon).getIntrinsicWidth();
doReturn(icon).when(icon).mutate();
return icon;
}
}
}

View File

@@ -0,0 +1,863 @@
/*
* Copyright (C) 2017 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.settingslib.applications;
import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
import static android.os.UserHandle.MU_ENABLED;
import static android.os.UserHandle.USER_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
import android.annotation.UserIdInt;
import android.app.Application;
import android.app.ApplicationPackageManager;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.ApplicationsState.Session;
import com.android.settingslib.testutils.shadow.ShadowUserManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextImpl;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUserManager.class,
ApplicationsStateRoboTest.ShadowIconDrawableFactory.class,
ApplicationsStateRoboTest.ShadowPackageManager.class})
public class ApplicationsStateRoboTest {
private final static String HOME_PACKAGE_NAME = "com.android.home";
private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable";
private static final int PROFILE_USERID = 10;
private static final int PROFILE_USERID2 = 11;
private static final String PKG_1 = "PKG1";
private static final int OWNER_UID_1 = 1001;
private static final int PROFILE_UID_1 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_1);
private static final String PKG_2 = "PKG2";
private static final int OWNER_UID_2 = 1002;
private static final int PROFILE_UID_2 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_2);
private static final String PKG_3 = "PKG3";
private static final int OWNER_UID_3 = 1003;
private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3);
private static final String CLONE_USER = "clone_user";
private static final String RANDOM_USER = "random_user";
/** Class under test */
private ApplicationsState mApplicationsState;
private Session mSession;
private Application mApplication;
@Spy
Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private Callbacks mCallbacks;
@Captor
private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor;
@Mock
private StorageStatsManager mStorageStatsManager;
@Mock
private IPackageManager mPackageManagerService;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Implements(value = IconDrawableFactory.class)
public static class ShadowIconDrawableFactory {
@Implementation
protected Drawable getBadgedIcon(ApplicationInfo appInfo) {
return new ColorDrawable(0);
}
}
@Implements(value = ApplicationPackageManager.class)
public static class ShadowPackageManager extends
org.robolectric.shadows.ShadowApplicationPackageManager {
// test installed modules, 2 regular, 2 hidden
private final String[] mModuleNames = {
"test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"};
private final List<ModuleInfo> mInstalledModules = new ArrayList<>();
@Implementation
protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
resolveInfo.activityInfo.packageName = HOME_PACKAGE_NAME;
resolveInfo.activityInfo.enabled = true;
outActivities.add(resolveInfo);
return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo");
}
@Implementation
public List<ModuleInfo> getInstalledModules(int flags) {
if (mInstalledModules.isEmpty()) {
for (String moduleName : mModuleNames) {
mInstalledModules.add(
createModuleInfo(moduleName,
TextUtils.concat(moduleName, ".apex").toString()));
}
}
return mInstalledModules;
}
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
@PackageManager.ResolveInfoFlagsBits int flags, @UserIdInt int userId) {
List<ResolveInfo> resolveInfos = new ArrayList<>();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
resolveInfo.activityInfo.packageName = LAUNCHABLE_PACKAGE_NAME;
resolveInfo.activityInfo.enabled = true;
resolveInfo.filter = new IntentFilter();
resolveInfo.filter.addCategory(Intent.CATEGORY_LAUNCHER);
resolveInfos.add(resolveInfo);
return resolveInfos;
}
private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
final ModuleInfo info = new ModuleInfo();
info.setName(packageName);
info.setPackageName(packageName);
info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
// will treat any app with package name that contains "hidden" as hidden module
info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
return info;
}
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// Robolectric does not know about the StorageStatsManager as a system service.
// Registering a mock of this service as a replacement.
ShadowContextImpl shadowContext = Shadow.extract(
RuntimeEnvironment.application.getBaseContext());
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
mApplication = spy(RuntimeEnvironment.application);
StorageStats storageStats = new StorageStats();
storageStats.codeBytes = 10;
storageStats.cacheBytes = 30;
// Data bytes are a superset of cache bytes.
storageStats.dataBytes = storageStats.cacheBytes + 20;
when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
anyString(), any(UserHandle.class))).thenReturn(storageStats);
// Set up 3 installed apps, in which 1 is hidden module
final List<ApplicationInfo> infos = new ArrayList<>();
infos.add(createApplicationInfo("test.package.1"));
infos.add(createApplicationInfo("test.hidden.module.2"));
infos.add(createApplicationInfo("test.package.3"));
when(mPackageManagerService.getInstalledApplications(
anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
ApplicationsState.sInstance = null;
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
mApplicationsState.clearEntries();
mSession = mApplicationsState.newSession(mCallbacks);
}
@After
public void tearDown() {
mSession.onDestroy();
}
private ApplicationInfo createApplicationInfo(String packageName) {
return createApplicationInfo(packageName, 0);
}
private ApplicationInfo createApplicationInfo(String packageName, int uid) {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.sourceDir = "foo";
appInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
appInfo.storageUuid = UUID.randomUUID();
appInfo.packageName = packageName;
appInfo.uid = uid;
return appInfo;
}
private AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
AppEntry appEntry = new AppEntry(RuntimeEnvironment.application, appInfo, id);
appEntry.label = "label";
appEntry.mounted = true;
return appEntry;
}
private void addApp(String packageName, int id) {
addApp(packageName, id, 0);
}
private void addApp(String packageName, int id, int userId) {
ApplicationInfo appInfo = createApplicationInfo(packageName, id);
AppEntry appEntry = createAppEntry(appInfo, id);
mApplicationsState.mAppEntries.add(appEntry);
mApplicationsState.mEntriesMap.get(userId).put(appInfo.packageName, appEntry);
}
private void processAllMessages() {
Handler mainHandler = mApplicationsState.mMainHandler;
Handler bkgHandler = mApplicationsState.mBackgroundHandler;
ShadowLooper shadowBkgLooper = extract(bkgHandler.getLooper());
ShadowLooper shadowMainLooper = extract(mainHandler.getLooper());
shadowBkgLooper.idle();
shadowMainLooper.idle();
}
private AppEntry findAppEntry(List<AppEntry> appEntries, long id) {
for (AppEntry appEntry : appEntries) {
if (appEntry.id == id) {
return appEntry;
}
}
return null;
}
@Test
public void testDefaultSession_isResumed_LoadsAll() {
mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
assertThat(appEntries.size()).isEqualTo(2);
for (AppEntry appEntry : appEntries) {
assertThat(appEntry.size).isGreaterThan(0L);
assertThat(appEntry.icon).isNotNull();
}
AppEntry homeEntry = findAppEntry(appEntries, 1);
assertThat(homeEntry.isHomeApp).isTrue();
assertThat(homeEntry.hasLauncherEntry).isFalse();
AppEntry launchableEntry = findAppEntry(appEntries, 2);
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
}
@Test
public void testDefaultSession_isPaused_NotLoadsAll() {
mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
mSession.mResumed = false;
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks, never()).onRebuildComplete(mAppEntriesCaptor.capture());
}
@Test
public void testCustomSessionLoadsIconsOnly() {
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
assertThat(appEntries.size()).isEqualTo(1);
AppEntry launchableEntry = findAppEntry(appEntries, 1);
assertThat(launchableEntry.icon).isNotNull();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.hasLauncherEntry).isFalse();
}
@Test
public void testCustomSessionLoadsSizesOnly() {
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
assertThat(appEntries.size()).isEqualTo(1);
AppEntry launchableEntry = findAppEntry(appEntries, 1);
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isGreaterThan(0L);
}
@Test
public void testCustomSessionLoadsHomeOnly() {
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
assertThat(appEntries.size()).isEqualTo(1);
AppEntry launchableEntry = findAppEntry(appEntries, 1);
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.isHomeApp).isTrue();
}
@Test
public void testCustomSessionLoadsLeanbackOnly() {
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
assertThat(appEntries.size()).isEqualTo(1);
AppEntry launchableEntry = findAppEntry(appEntries, 1);
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.isHomeApp).isFalse();
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
}
@Test
public void onResume_shouldNotIncludeSystemHiddenModule() {
mSession.onResume();
final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
assertThat(mApplications).hasSize(2);
assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3");
}
@Test
public void removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
// scenario: only owner user
// (PKG_1, PKG_2) -> (PKG_2, PKG_3)
// PKG_1 is removed and PKG_3 is installed before app is resumed.
ApplicationsState.sInstance = null;
mApplicationsState = spy(
ApplicationsState
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
// Previous Applications:
ApplicationInfo appInfo;
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
prevAppList.add(appInfo);
mApplicationsState.mApplications = prevAppList;
// Previous Entries:
// (PKG_1, PKG_2)
addApp(PKG_1, OWNER_UID_1, 0);
addApp(PKG_2, OWNER_UID_2, 0);
// latest Applications:
// (PKG_2, PKG_3)
final ArrayList<ApplicationInfo> appList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
appList.add(appInfo);
appInfo = createApplicationInfo(PKG_3, OWNER_UID_3);
appList.add(appInfo);
setupDoResumeIfNeededLocked(appList, null);
mApplicationsState.doResumeIfNeededLocked();
verify(mApplicationsState).clearEntries();
}
@Test
public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries()
throws RemoteException {
// scenario: only owner user
// (PKG_1, PKG_2)
ApplicationsState.sInstance = null;
mApplicationsState = spy(
ApplicationsState
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
ApplicationInfo appInfo;
// Previous Applications
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
prevAppList.add(appInfo);
mApplicationsState.mApplications = prevAppList;
// Previous Entries:
// (pk1, PKG_2)
addApp(PKG_1, OWNER_UID_1, 0);
addApp(PKG_2, OWNER_UID_2, 0);
// latest Applications:
// (PKG_2, PKG_3)
final ArrayList<ApplicationInfo> appList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
appList.add(appInfo);
setupDoResumeIfNeededLocked(appList, null);
mApplicationsState.doResumeIfNeededLocked();
verify(mApplicationsState, never()).clearEntries();
}
@Test
public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
if (!MU_ENABLED) {
return;
}
// [Preconditions]
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
// 2 apps (PKG_1, PKG_2) for non-owner.
//
// [Actions]
// profile user's PKG_2 is removed before resume
//
// Applications:
// owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2)
// profile - (PKG_1, PKG_2) -> (PKG_1)
//
// Previous Entries:
// owner - (PKG_2)
// profile - (PKG_1, PKG_2)
ShadowUserManager shadowUserManager = Shadow
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
shadowUserManager.addProfile(PROFILE_USERID, "profile");
ApplicationsState.sInstance = null;
mApplicationsState = spy(
ApplicationsState
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
ApplicationInfo appInfo;
// Previous Applications
// owner - (PKG_1 - uninstalled, PKG_2)
// profile - (PKG_1, PKG_2)
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
prevAppList.add(appInfo);
mApplicationsState.mApplications = prevAppList;
// Previous Entries:
// owner (PKG_2), profile (pk1, PKG_2)
// PKG_1 is not installed for owner, hence it's removed from entries
addApp(PKG_2, OWNER_UID_2, 0);
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
// latest Applications:
// owner (PKG_1, PKG_2), profile (PKG_1)
// owner's PKG_1 is still listed and is in non-installed state
// profile user's PKG_2 is removed by a user before resume
//owner
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
ownerAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
ownerAppList.add(appInfo);
//profile
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
setupDoResumeIfNeededLocked(ownerAppList, new ArrayList<>(Arrays.asList(appInfo)));
mApplicationsState.doResumeIfNeededLocked();
verify(mApplicationsState).clearEntries();
}
@Test
public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
if (!MU_ENABLED) {
return;
}
// [Preconditions]
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
// 2 apps (PKG_1, PKG_2) for non-owner.
//
// [Actions]
// Owner user's PKG_2 is removed before resume
//
// Applications:
// owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2 - uninstalled)
// profile - (PKG_1, PKG_2) -> (PKG_1, PKG_2)
//
// Previous Entries:
// owner - (PKG_2)
// profile - (PKG_1, PKG_2)
ShadowUserManager shadowUserManager = Shadow
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
shadowUserManager.addProfile(PROFILE_USERID, "profile");
ApplicationsState.sInstance = null;
mApplicationsState = spy(
ApplicationsState
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
ApplicationInfo appInfo;
// Previous Applications:
// owner - (PKG_1 - uninstalled, PKG_2)
// profile - (PKG_1, PKG_2)
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
prevAppList.add(appInfo);
mApplicationsState.mApplications = prevAppList;
// Previous Entries:
// owner (PKG_2), profile (pk1, PKG_2)
// PKG_1 is not installed for owner, hence it's removed from entries
addApp(PKG_2, OWNER_UID_2, 0);
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
// latest Applications:
// owner (PKG_1 - uninstalled, PKG_2 - uninstalled), profile (PKG_1, PKG_2)
// owner's PKG_1, PKG_2 is still listed and is in non-installed state
// profile user's PKG_2 is removed before resume
//owner
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
ownerAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
ownerAppList.add(appInfo);
//profile
final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
profileAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
profileAppList.add(appInfo);
setupDoResumeIfNeededLocked(ownerAppList, profileAppList);
mApplicationsState.doResumeIfNeededLocked();
verify(mApplicationsState).clearEntries();
}
@Test
public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
throws RemoteException {
if (!MU_ENABLED) {
return;
}
// [Preconditions]
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
// 2 apps (PKG_1, PKG_2) for non-owner.
//
// Applications:
// owner - (PKG_1 - uninstalled, PKG_2)
// profile - (PKG_1, PKG_2)
//
// Previous Entries:
// owner - (PKG_2)
// profile - (PKG_1, PKG_2)
ShadowUserManager shadowUserManager = Shadow
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
shadowUserManager.addProfile(PROFILE_USERID, "profile");
ApplicationsState.sInstance = null;
mApplicationsState = spy(
ApplicationsState
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
ApplicationInfo appInfo;
// Previous Applications:
// owner - (PKG_1 - uninstalled, PKG_2)
// profile - (PKG_1, PKG_2)
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
prevAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
prevAppList.add(appInfo);
mApplicationsState.mApplications = prevAppList;
// Previous Entries:
// owner (PKG_2), profile (pk1, PKG_2)
// PKG_1 is not installed for owner, hence it's removed from entries
addApp(PKG_2, OWNER_UID_2, 0);
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
// latest Applications:
// owner (PKG_1 - uninstalled, PKG_2), profile (PKG_1, PKG_2)
// owner's PKG_1 is still listed and is in non-installed state
// owner
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
ownerAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
ownerAppList.add(appInfo);
// profile
final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>();
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
profileAppList.add(appInfo);
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
profileAppList.add(appInfo);
setupDoResumeIfNeededLocked(ownerAppList, profileAppList);
mApplicationsState.doResumeIfNeededLocked();
verify(mApplicationsState, never()).clearEntries();
}
@Test
public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
when(mApplication.getPackageName()).thenReturn("com.android.settings");
mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
for (AppEntry appEntry : appEntries) {
assertThat(appEntry.icon).isNull();
}
}
private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
ArrayList<ApplicationInfo> profileApps)
throws RemoteException {
if (ownerApps != null) {
when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0)))
.thenReturn(new ParceledListSlice<>(ownerApps));
}
if (profileApps != null) {
when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID)))
.thenReturn(new ParceledListSlice<>(profileApps));
}
final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class);
when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false);
mApplicationsState.setInterestingConfigChanges(configChanges);
}
@Test
public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
ApplicationInfo appInfo = createApplicationInfo(PKG_1);
AppEntry primaryUserApp = createAppEntry(appInfo, 1);
assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
}
@Test
public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.TIRAMISU);
// Create an app (and subsequent AppEntry) in a non-primary user profile.
ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse();
}
@Test
public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
// Mark system user as parent for both profile users.
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
ShadowUserManager shadowUserManager = Shadow.extract(um);
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
CLONE_USER, 0);
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
RANDOM_USER, 0);
shadowUserManager.setupUserProperty(PROFILE_USERID,
/*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT);
shadowUserManager.setupUserProperty(PROFILE_USERID2,
/*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO);
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
// Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it.
ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
// Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it.
ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3);
AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue();
assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse();
}
@Test
public void getEntry_hasCache_shouldReturnCacheEntry() {
mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
addApp(PKG_1, /* id= */ 1);
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
.isEqualTo(PKG_1);
}
@Test
public void getEntry_hasNoCache_shouldReturnEntry() {
mApplicationsState.mEntriesMap.clear();
ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
mApplicationsState.mApplications.add(appInfo);
mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
.isEqualTo(PKG_1);
}
@Test
public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
ApplicationsState.sInstance = null;
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
String normalModulePackage = "test.module.1";
String hiddenModulePackage = "test.hidden.module.2";
String hiddenApexPackage = "test.hidden.module.2.apex";
assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
}
@Test
public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
ApplicationsState.sInstance = null;
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
String normalModulePackage = "test.module.1";
String hiddenModulePackage = "test.hidden.module.2";
String hiddenApexPackage = "test.hidden.module.2.apex";
assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 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.settingslib.applications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
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;
@RunWith(RobolectricTestRunner.class)
public class DefaultAppInfoTest {
@Mock
private PackageItemInfo mPackageItemInfo;
@Mock
private ComponentName mComponentName;
@Mock
private PackageManager mPackageManager;
@Mock
private ApplicationInfo mApplicationInfo;
@Mock
private Drawable mIcon;
private Context mContext;
private DefaultAppInfo mInfo;
@Before
public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
doReturn(mPackageManager).when(mContext).getPackageManager();
when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(),
anyInt())).thenReturn(mApplicationInfo);
when(mPackageManager.loadUnbadgedItemIcon(mPackageItemInfo, mApplicationInfo)).thenReturn(
mIcon);
}
@Test
public void initInfoWithActivityInfo_shouldLoadInfo() {
mPackageItemInfo.packageName = "test";
mInfo = new DefaultAppInfo(mContext, mPackageManager, 0 /* uid */, mPackageItemInfo);
mInfo.loadLabel();
Drawable icon = mInfo.loadIcon();
assertThat(mInfo.getKey()).isEqualTo(mPackageItemInfo.packageName);
assertThat(icon).isNotNull();
verify(mPackageItemInfo).loadLabel(mPackageManager);
}
@Test
public void initInfoWithComponent_shouldLoadInfo() {
when(mComponentName.getPackageName()).thenReturn("com.android.settings");
mInfo = new DefaultAppInfo(mContext, mPackageManager, 0 /* uid */, mComponentName);
mInfo.getKey();
verify(mComponentName).flattenToString();
}
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2021 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.settingslib.applications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.LongSparseArray;
import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
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.annotation.Config;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowPermissionChecker.class})
public class RecentAppOpsAccessesTest {
private static final int TEST_UID = 1234;
private static final long NOW = 1_000_000_000; // Approximately 9/8/2001
private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
private static final long TWENTY_THREE_HOURS_AGO = NOW - TimeUnit.HOURS.toMillis(23);
private static final long TWO_DAYS_AGO = NOW - TimeUnit.DAYS.toMillis(2);
private static final String[] TEST_PACKAGE_NAMES =
{"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
@Mock
private PackageManager mPackageManager;
@Mock
private AppOpsManager mAppOpsManager;
@Mock
private UserManager mUserManager;
@Mock
private Clock mClock;
private Context mContext;
private int mTestUserId;
private RecentAppOpsAccess mRecentAppOpsAccess;
@Before
public void setUp() throws NameNotFoundException {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
.thenReturn("testApplicationLabel");
when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
.thenReturn("testUserBadgedLabel");
when(mPackageManager.getPermissionFlags(any(), any(), any()))
.thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
| PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED);
for (String testPackageName : TEST_PACKAGE_NAMES) {
ShadowPermissionChecker.setResult(
testPackageName,
Manifest.permission.ACCESS_COARSE_LOCATION,
PermissionChecker.PERMISSION_GRANTED);
ShadowPermissionChecker.setResult(
testPackageName,
Manifest.permission.ACCESS_FINE_LOCATION,
PermissionChecker.PERMISSION_GRANTED);
}
mTestUserId = UserHandle.getUserId(TEST_UID);
when(mUserManager.getUserProfiles())
.thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
long[] testRequestTime = {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO};
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
appOps);
mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
when(mClock.millis()).thenReturn(NOW);
mRecentAppOpsAccess = new RecentAppOpsAccess(mContext, mClock,
RecentAppOpsAccess.LOCATION_OPS);
}
@Test
public void testGetAppList_shouldFilterRecentAccesses() {
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(false);
// Only two of the apps have requested location within 15 min.
assertThat(requests).hasSize(2);
// Make sure apps are ordered by recency
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
assertThat(requests.get(0).accessFinishTime).isEqualTo(ONE_MIN_AGO);
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
assertThat(requests.get(1).accessFinishTime).isEqualTo(TWENTY_THREE_HOURS_AGO);
}
@Test
public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
// Add android OS to the list of apps.
PackageOps androidSystemPackageOps =
createPackageOps(
RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME,
Process.SYSTEM_UID,
AppOpsManager.OP_FINE_LOCATION,
ONE_MIN_AGO);
long[] testRequestTime =
{ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO, ONE_MIN_AGO};
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
appOps.add(androidSystemPackageOps);
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
appOps);
mockTestApplicationInfos(
Process.SYSTEM_UID, RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME);
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(true);
// Android OS shouldn't show up in the list of apps.
assertThat(requests).hasSize(2);
// Make sure apps are ordered by recency
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
assertThat(requests.get(0).accessFinishTime).isEqualTo(ONE_MIN_AGO);
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
assertThat(requests.get(1).accessFinishTime).isEqualTo(TWENTY_THREE_HOURS_AGO);
}
private void mockTestApplicationInfos(int userId, String... packageNameList)
throws NameNotFoundException {
for (String packageName : packageNameList) {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.packageName = packageName;
when(mPackageManager.getApplicationInfoAsUser(
packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
}
}
private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
List<PackageOps> packageOpsList = new ArrayList<>();
for (int i = 0; i < packageNameList.length; i++) {
PackageOps packageOps = createPackageOps(
packageNameList[i],
TEST_UID,
AppOpsManager.OP_FINE_LOCATION,
time[i]);
packageOpsList.add(packageOps);
}
return packageOpsList;
}
private PackageOps createPackageOps(String packageName, int uid, int op, long time) {
return new PackageOps(
packageName,
uid,
Collections.singletonList(createOpEntryWithTime(op, time)));
}
private OpEntry createOpEntryWithTime(int op, long time) {
// Slot for background access timestamp.
final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
new AppOpsManager.AttributedOpEntry(op, false, accessEvents, null)));
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2017 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.settingslib.applications;
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.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class ServiceListingTest {
private static final String TEST_SETTING = "testSetting";
private static final String TEST_INTENT = "com.example.intent";
private ServiceListing mServiceListing;
private Context mContext;
private PackageManager mPm;
@Before
public void setUp() {
mPm = mock(PackageManager.class);
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getPackageManager()).thenReturn(mPm);
mServiceListing = new ServiceListing.Builder(mContext)
.setTag("testTag")
.setSetting(TEST_SETTING)
.setNoun("testNoun")
.setIntentAction(TEST_INTENT)
.setPermission("testPermission")
.build();
}
@Test
public void testValidator() {
ServiceInfo s1 = new ServiceInfo();
s1.permission = "testPermission";
s1.packageName = "pkg";
ServiceInfo s2 = new ServiceInfo();
s2.permission = "testPermission";
s2.packageName = "pkg2";
ResolveInfo r1 = new ResolveInfo();
r1.serviceInfo = s1;
ResolveInfo r2 = new ResolveInfo();
r2.serviceInfo = s2;
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
ImmutableList.of(r1, r2));
mServiceListing = new ServiceListing.Builder(mContext)
.setTag("testTag")
.setSetting(TEST_SETTING)
.setNoun("testNoun")
.setIntentAction(TEST_INTENT)
.setValidator(info -> {
if (info.packageName.equals("pkg")) {
return true;
}
return false;
})
.setPermission("testPermission")
.build();
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
mServiceListing.addCallback(callback);
mServiceListing.reload();
verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(callback, times(1)).onServicesReloaded(captor.capture());
assertThat(captor.getValue().size()).isEqualTo(1);
assertThat(captor.getValue().get(0)).isEqualTo(s1);
}
@Test
public void testNoValidator() {
ServiceInfo s1 = new ServiceInfo();
s1.permission = "testPermission";
s1.packageName = "pkg";
ServiceInfo s2 = new ServiceInfo();
s2.permission = "testPermission";
s2.packageName = "pkg2";
ResolveInfo r1 = new ResolveInfo();
r1.serviceInfo = s1;
ResolveInfo r2 = new ResolveInfo();
r2.serviceInfo = s2;
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
ImmutableList.of(r1, r2));
mServiceListing = new ServiceListing.Builder(mContext)
.setTag("testTag")
.setSetting(TEST_SETTING)
.setNoun("testNoun")
.setIntentAction(TEST_INTENT)
.setPermission("testPermission")
.build();
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
mServiceListing.addCallback(callback);
mServiceListing.reload();
verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(callback, times(1)).onServicesReloaded(captor.capture());
assertThat(captor.getValue().size()).isEqualTo(2);
}
@Test
public void testCallback() {
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
mServiceListing.addCallback(callback);
mServiceListing.reload();
verify(callback, times(1)).onServicesReloaded(anyList());
mServiceListing.removeCallback(callback);
mServiceListing.reload();
verify(callback, times(1)).onServicesReloaded(anyList());
}
@Test
public void testSaveLoad() {
ComponentName testComponent1 = new ComponentName("testPackage1", "testClass1");
ComponentName testComponent2 = new ComponentName("testPackage2", "testClass2");
Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING,
testComponent1.flattenToString() + ":" + testComponent2.flattenToString());
mServiceListing.reload();
assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent1.flattenToString());
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent2.flattenToString());
mServiceListing.setEnabled(testComponent1, false);
assertThat(mServiceListing.isEnabled(testComponent1)).isFalse();
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).doesNotContain(testComponent1.flattenToString());
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent2.flattenToString());
mServiceListing.setEnabled(testComponent1, true);
assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent1.flattenToString());
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent2.flattenToString());
}
}