diff options
| author | 2024-01-29 14:13:28 +0000 | |
|---|---|---|
| committer | 2024-02-01 12:07:52 +0000 | |
| commit | 6c552d6e5233c7db4745d747eb41e3a8235c908f (patch) | |
| tree | 30cb31541793c589e4337fe35985b8333190ca86 | |
| parent | f23d375e4f15262bd951cd059a9461310ba97ba9 (diff) | |
Hide hidden apk-in-Apex of mainline modules from the app list
- Update the legacy app list behavior
- Update the SPA app list behavior
- Refactor isMainlineModule() with new support interface
Bug: 273913035
Test: manual & atest
Change-Id: I880f7ce92cf88325f8bd30e2fb2e29e1033da42c
7 files changed, 162 insertions, 17 deletions
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index 0a98791e8a6a..988afd70aaed 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -17,10 +17,11 @@ package com.android.settingslib.spaprivileged.model.app import android.content.Context -import android.content.pm.FeatureFlags -import android.content.pm.FeatureFlagsImpl import android.content.Intent import android.content.pm.ApplicationInfo +import android.content.pm.FeatureFlags +import android.content.pm.FeatureFlagsImpl +import android.content.pm.Flags import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo @@ -85,13 +86,7 @@ class AppListRepositoryImpl( loadInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = coroutineScope { - val hiddenSystemModulesDeferred = async { - packageManager.getInstalledModules(0) - .filter { it.isHidden } - .map { it.packageName } - .filterNotNull() - .toSet() - } + val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() } val hideWhenDisabledPackagesDeferred = async { context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) } @@ -205,6 +200,15 @@ class AppListRepositoryImpl( private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean = app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages + private fun PackageManager.getHiddenSystemModules(): Set<String> { + val moduleInfos = getInstalledModules(0).filter { it.isHidden } + val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet() + if (Flags.provideInfoOfApkInApex()) { + hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames } + } + return hiddenApps + } + companion object { private fun ApplicationInfo.isInAppList( showInstantApps: Boolean, diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp index a28ebc6923bf..458fcc97aa9b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp +++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp @@ -35,5 +35,6 @@ android_test { "androidx.test.ext.junit", "androidx.test.runner", "mockito-target-minus-junit4", + "flag-junit", ], } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index f2922316dbde..efd53a4c9c23 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -21,6 +21,7 @@ import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.FakeFeatureFlagsImpl import android.content.pm.Flags +import android.content.pm.ModuleInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.PackageManager.ResolveInfoFlags @@ -28,6 +29,7 @@ import android.content.pm.ResolveInfo import android.content.pm.UserInfo import android.content.res.Resources import android.os.UserManager +import android.platform.test.flag.junit.SetFlagsRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.R @@ -35,6 +37,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any @@ -50,6 +53,9 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppListRepositoryTest { + @get:Rule + val mSetFlagsRule = SetFlagsRule() + private val resources = mock<Resources> { on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray() } @@ -273,6 +279,38 @@ class AppListRepositoryTest { } @Test + fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest { + mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) + packageManager.stub { + on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE) + } + mockInstalledApplications( + listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP), + ADMIN_USER_ID + ) + + val appList = repository.loadApps(userId = ADMIN_USER_ID) + + assertThat(appList).containsExactly(NORMAL_APP) + } + + @Test + fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest { + mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) + packageManager.stub { + on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE) + } + mockInstalledApplications( + listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP), + ADMIN_USER_ID + ) + + val appList = repository.loadApps(userId = ADMIN_USER_ID) + + assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP) + } + + @Test fun showSystemPredicate_showSystem() = runTest { val app = SYSTEM_APP @@ -402,6 +440,20 @@ class AppListRepositoryTest { isArchived = true } + val HIDDEN_APEX_APP = ApplicationInfo().apply { + packageName = "hidden.apex.package" + } + + val HIDDEN_MODULE_APP = ApplicationInfo().apply { + packageName = "hidden.module.package" + } + + val HIDDEN_MODULE = ModuleInfo().apply { + packageName = "hidden.module.package" + apkInApexPackageNames = listOf("hidden.apex.package") + isHidden = true + } + fun resolveInfoOf(packageName: String) = ResolveInfo().apply { activityInfo = ActivityInfo().apply { this.packageName = packageName diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index 07de7fd6438e..c4829951d61a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -164,12 +165,16 @@ public class AppUtils { try { final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); - // Check if the package is contained in an APEX. There is no public API to properly - // check whether a given APK package comes from an APEX registered as module. - // Therefore we conservatively assume that any package scanned from an /apex path is - // a system package. - return pkg.applicationInfo.sourceDir.startsWith( - Environment.getApexDirectory().getAbsolutePath()); + if (Flags.provideInfoOfApkInApex()) { + return pkg.getApexPackageName() != null; + } else { + // Check if the package is contained in an APEX. There is no public API to properly + // check whether a given APK package comes from an APEX registered as module. + // Therefore we conservatively assume that any package scanned from an /apex path is + // a system package. + return pkg.applicationInfo.sourceDir.startsWith( + Environment.getApexDirectory().getAbsolutePath()); + } } catch (PackageManager.NameNotFoundException e) { return false; } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 8e1067f28ffb..e3012cd3e6fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.IPackageManager; import android.content.pm.IPackageStatsObserver; import android.content.pm.ModuleInfo; @@ -226,6 +227,11 @@ public class ApplicationsState { final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */); for (ModuleInfo info : moduleInfos) { mSystemModules.put(info.getPackageName(), info.isHidden()); + if (Flags.provideInfoOfApkInApex()) { + for (String apkInApexPackageName : info.getApkInApexPackageNames()) { + mSystemModules.put(apkInApexPackageName, info.isHidden()); + } + } } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java index 994c1ee5013f..e32d88069238 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java @@ -16,6 +16,8 @@ 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; @@ -24,12 +26,16 @@ 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; @@ -39,6 +45,8 @@ 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; @@ -60,6 +68,10 @@ public class AppUtilsTest { 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() { @@ -70,6 +82,7 @@ public class AppUtilsTest { mAppEntry = createAppEntry(mAppInfo, /* id= */ 1); mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry)); doReturn(mIcon).when(mIcon).mutate(); + mShadowPackageManager = Shadow.extract(mContext.getPackageManager()); } @After @@ -151,6 +164,32 @@ public class AppUtilsTest { 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"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 34d8148f418f..827d8fa9e7c1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -16,6 +16,7 @@ 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; @@ -58,6 +59,7 @@ 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; @@ -70,6 +72,7 @@ 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; @@ -89,6 +92,7 @@ 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; @@ -137,6 +141,9 @@ public class ApplicationsStateRoboTest { @Mock private IPackageManager mPackageManagerService; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Implements(value = IconDrawableFactory.class) public static class ShadowIconDrawableFactory { @@ -169,7 +176,9 @@ public class ApplicationsStateRoboTest { public List<ModuleInfo> getInstalledModules(int flags) { if (mInstalledModules.isEmpty()) { for (String moduleName : mModuleNames) { - mInstalledModules.add(createModuleInfo(moduleName)); + mInstalledModules.add( + createModuleInfo(moduleName, + TextUtils.concat(moduleName, ".apex").toString())); } } return mInstalledModules; @@ -188,10 +197,11 @@ public class ApplicationsStateRoboTest { return resolveInfos; } - private ModuleInfo createModuleInfo(String packageName) { + 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; @@ -822,4 +832,32 @@ public class ApplicationsStateRoboTest { 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(); + } } |