fix: 首次提交
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user