From 1867bc81e2551f71c6f5e2d335c5edc664a4186c Mon Sep 17 00:00:00 2001 From: Winson Chiu Date: Wed, 1 Apr 2020 18:20:51 +0000 Subject: [PATCH 1/3] Revert^2 "Add test constructor to PackageManagerService" 835348a5dafc501b178e4ac2a7a92185da6c6379 Exempt-From-Owner-Approval: Re-submit of approved change, no changes. Bug: 113638339 Change-Id: I0a44f88784eb96f59033cb01adb4e093d67c4645 --- .../server/pm/PackageManagerService.java | 139 +++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8850f29e9bc26..0965878b23dd2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -674,7 +674,7 @@ public class PackageManagerService extends IPackageManager.Stub final ServiceThread mHandlerThread; - final PackageHandler mHandler; + final Handler mHandler; private final ProcessLoggingHandler mProcessLoggingHandler; @@ -1033,6 +1033,61 @@ public class PackageManagerService extends IPackageManager.Stub } } + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class TestParams { + public ApexManager apexManager; + public @Nullable String appPredictionServicePackage; + public ArtManagerService artManagerService; + public @Nullable String configuratorPackage; + public int defParseFlags; + public DexManager dexManager; + public List dirsToScanAsSystem; + public @Nullable String documenterPackage; + public boolean factoryTest; + public ArrayMap availableFeatures; + public Handler handler; + public ServiceThread handlerThread; + public @Nullable String incidentReportApproverPackage; + public IncrementalManager incrementalManager; + public PackageInstallerService installerService; + public InstantAppRegistry instantAppRegistry; + public InstantAppResolverConnection instantAppResolverConnection; + public ComponentName instantAppResolverSettingsComponent; + public @Nullable IntentFilterVerifier intentFilterVerifier; + public @Nullable ComponentName intentFilterVerifierComponent; + public boolean isPreNmr1Upgrade; + public boolean isPreNupgrade; + public boolean isPreQupgrade; + public boolean isUpgrade; + public DisplayMetrics Metrics; + public ModuleInfoProvider moduleInfoProvider; + public MoveCallbacks moveCallbacks; + public boolean onlyCore; + public OverlayConfig overlayConfig; + public PackageDexOptimizer packageDexOptimizer; + public IPermissionManager permissionManagerService; + public PendingPackageBroadcasts pendingPackageBroadcasts; + public PackageManagerInternal pmInternal; + public ProcessLoggingHandler processLoggingHandler; + public ProtectedPackages protectedPackages; + public @NonNull String requiredInstallerPackage; + public @NonNull String requiredPermissionControllerPackage; + public @NonNull String requiredUninstallerPackage; + public @Nullable String requiredVerifierPackage; + public String[] separateProcesses; + public @NonNull String servicesExtensionPackageName; + public @Nullable String setupWizardPackage; + public @NonNull String sharedSystemSharedLibraryPackageName; + public @Nullable String storageManagerPackage; + public @Nullable String defaultTextClassifierPackage; + public @Nullable String systemTextClassifierPackage; + public ViewCompiler viewCompiler; + public @Nullable String wellbeingPackage; + public @Nullable String retailDemoPackage; + public ComponentName resolveComponentName; + public ArrayMap packages; + } + private final AppsFilter mAppsFilter; final PackageParser2.Callback mPackageParserCallback; @@ -1396,7 +1451,8 @@ public class PackageManagerService extends IPackageManager.Stub } // Set of pending broadcasts for aggregating enable/disable of components. - static class PendingPackageBroadcasts { + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static class PendingPackageBroadcasts { // for each user id, a map of components within that package> final SparseArray>> mUidMap; @@ -1459,7 +1515,7 @@ public class PackageManagerService extends IPackageManager.Stub return map; } } - final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts(); + final PendingPackageBroadcasts mPendingBroadcasts; static final int SEND_PENDING_BROADCAST = 1; static final int INIT_COPY = 5; @@ -2676,6 +2732,81 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * A extremely minimal constructor designed to start up a PackageManagerService instance for + * testing. + * + * It is assumed that all methods under test will mock the internal fields and thus + * none of the initialization is needed. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public PackageManagerService(@NonNull Injector injector, @NonNull TestParams testParams) { + mInjector = injector; + mInjector.bootstrap(this); + mAppsFilter = injector.getAppsFilter(); + mComponentResolver = injector.getComponentResolver(); + mContext = injector.getContext(); + mInstaller = injector.getInstaller(); + mInstallLock = injector.getInstallLock(); + mLock = injector.getLock(); + mPermissionManager = injector.getPermissionManagerServiceInternal(); + mSettings = injector.getSettings(); + mUserManager = injector.getUserManagerService(); + + mApexManager = testParams.apexManager; + mArtManagerService = testParams.artManagerService; + mAvailableFeatures = testParams.availableFeatures; + mDefParseFlags = testParams.defParseFlags; + mDexManager = testParams.dexManager; + mDirsToScanAsSystem = testParams.dirsToScanAsSystem; + mFactoryTest = testParams.factoryTest; + mHandler = testParams.handler; + mHandlerThread = testParams.handlerThread; + mIncrementalManager = testParams.incrementalManager; + mInstallerService = testParams.installerService; + mInstantAppRegistry = testParams.instantAppRegistry; + mInstantAppResolverConnection = testParams.instantAppResolverConnection; + mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent; + mIntentFilterVerifier = testParams.intentFilterVerifier; + mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent; + mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade; + mIsPreNUpgrade = testParams.isPreNupgrade; + mIsPreQUpgrade = testParams.isPreQupgrade; + mIsUpgrade = testParams.isUpgrade; + mMetrics = testParams.Metrics; + mModuleInfoProvider = testParams.moduleInfoProvider; + mMoveCallbacks = testParams.moveCallbacks; + mOnlyCore = testParams.onlyCore; + mOverlayConfig = testParams.overlayConfig; + mPackageDexOptimizer = testParams.packageDexOptimizer; + mPendingBroadcasts = testParams.pendingPackageBroadcasts; + mPermissionManagerService = testParams.permissionManagerService; + mPmInternal = testParams.pmInternal; + mProcessLoggingHandler = testParams.processLoggingHandler; + mProtectedPackages = testParams.protectedPackages; + mSeparateProcesses = testParams.separateProcesses; + mViewCompiler = testParams.viewCompiler; + mRequiredVerifierPackage = testParams.requiredVerifierPackage; + mRequiredInstallerPackage = testParams.requiredInstallerPackage; + mRequiredUninstallerPackage = testParams.requiredUninstallerPackage; + mRequiredPermissionControllerPackage = testParams.requiredPermissionControllerPackage; + mSetupWizardPackage = testParams.setupWizardPackage; + mStorageManagerPackage = testParams.storageManagerPackage; + mDefaultTextClassifierPackage = testParams.defaultTextClassifierPackage; + mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage; + mWellbeingPackage = testParams.wellbeingPackage; + mRetailDemoPackage = testParams.retailDemoPackage; + mDocumenterPackage = testParams.documenterPackage; + mConfiguratorPackage = testParams.configuratorPackage; + mAppPredictionServicePackage = testParams.appPredictionServicePackage; + mIncidentReportApproverPackage = testParams.incidentReportApproverPackage; + mServicesExtensionPackageName = testParams.servicesExtensionPackageName; + mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName; + + mResolveComponentName = testParams.resolveComponentName; + mPackages.putAll(testParams.packages); + } + public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) { PackageManager.invalidatePackageInfoCache(); PackageManager.disableApplicationInfoCache(); @@ -2683,6 +2814,8 @@ public class PackageManagerService extends IPackageManager.Stub final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", Trace.TRACE_TAG_PACKAGE_MANAGER); + mPendingBroadcasts = new PendingPackageBroadcasts(); + mInjector = injector; mInjector.bootstrap(this); mLock = injector.getLock(); From 8e865ecd066d05bdb1ff23f3337b39c8a558555f Mon Sep 17 00:00:00 2001 From: Winson Chiu Date: Wed, 1 Apr 2020 18:20:51 +0000 Subject: [PATCH 2/3] Revert "Revert "Allow overriding the label and icon of a MainCom..." Revert submission 10921255-revert-10403399-pkg-override-label-icon-DCRMJNYAKW Reason for revert: Re-submit broken change with included fix. Reverted Changes: Ic5c719cbb:Revert "Allow overriding the label and icon of a M... Id9d37e661:Revert "Add test constructor to PackageManagerServ... Bug: 113638339 Change-Id: I4f5247f528f62c175bd34f0e4cb16c9456c8afe4 --- .../android/content/pm/IPackageManager.aidl | 29 ++ .../android/content/pm/PackageUserState.java | 71 ++++ core/res/res/values/config.xml | 3 + core/res/res/values/symbols.xml | 2 + .../android/server/pm/ComponentResolver.java | 33 +- .../server/pm/PackageManagerService.java | 88 ++++- .../com/android/server/pm/PackageSetting.java | 4 +- .../android/server/pm/PackageSettingBase.java | 23 ++ .../com/android/server/pm/SettingBase.java | 4 +- .../java/com/android/server/pm/Settings.java | 21 +- .../server/pm/parsing/PackageInfoUtils.java | 22 +- .../pm/parsing/ParsedComponentStateUtils.java | 58 +++ .../Android.bp | 40 ++ .../AndroidManifest.xml | 34 ++ .../AndroidTest.xml | 29 ++ .../res/drawable/black16x16.png | Bin 0 -> 260 bytes .../res/drawable/white16x16.png | Bin 0 -> 262 bytes .../res/values/values.xml | 21 + ...geManagerComponentLabelIconOverrideTest.kt | 358 ++++++++++++++++++ services/tests/servicestests/Android.bp | 18 + .../server/om/OverlayReferenceMapperTests.kt | 2 + .../parsing/AndroidPackageParsingTestBase.kt | 4 +- .../android/server/testutils}/MockitoUtils.kt | 20 +- .../android/server/testutils/TestHandler.java | 2 +- 24 files changed, 862 insertions(+), 24 deletions(-) create mode 100644 services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java create mode 100644 services/tests/PackageManagerComponentOverrideTests/Android.bp create mode 100644 services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml create mode 100644 services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml create mode 100644 services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png create mode 100644 services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png create mode 100644 services/tests/PackageManagerComponentOverrideTests/res/values/values.xml create mode 100644 services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt rename services/tests/servicestests/{src/com/android/server/om => utils-mockito/com/android/server/testutils}/MockitoUtils.kt (75%) diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 5bad055810ccc..8bebafff37f02 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -305,6 +305,35 @@ interface IPackageManager { void setHomeActivity(in ComponentName className, int userId); + /** + * Overrides the label and icon of the component specified by the component name. The component + * must belong to the calling app. + * + * These changes will be reset on the next boot and whenever the package is updated. + * + * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed + * to call this. + * + * @param componentName The component name to override the label/icon of. + * @param nonLocalizedLabel The label to be displayed. + * @param icon The icon to be displayed. + * @param userId The user id. + */ + void overrideLabelAndIcon(in ComponentName componentName, String nonLocalizedLabel, + int icon, int userId); + + /** + * Restores the label and icon of the activity specified by the component name if either has + * been overridden. The component must belong to the calling app. + * + * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed + * to call this. + * + * @param componentName The component name. + * @param userId The user id. + */ + void restoreLabelAndIcon(in ComponentName componentName, int userId); + /** * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}. */ diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 61b1553e28a8a..327d1b8beeb12 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -27,18 +27,24 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.pm.parsing.component.ParsedMainComponent; import android.os.BaseBundle; import android.os.Debug; import android.os.PersistableBundle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,6 +90,9 @@ public class PackageUserState { private ArrayMap sharedLibraryOverlayPaths; // Lib name to overlay paths private String[] cachedOverlayPaths; + @Nullable + private ArrayMap> componentLabelIconOverrideMap; + @UnsupportedAppUsage public PackageUserState() { installed = true; @@ -123,6 +132,9 @@ public class PackageUserState { sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths); } harmfulAppWarning = o.harmfulAppWarning; + if (o.componentLabelIconOverrideMap != null) { + this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap); + } } public String[] getOverlayPaths() { @@ -146,6 +158,65 @@ public class PackageUserState { cachedOverlayPaths = null; } + /** + * Overrides the non-localized label and icon of a component. + * + * @return true if the label or icon was changed. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean overrideLabelAndIcon(@NonNull ComponentName component, + @Nullable String nonLocalizedLabel, @Nullable Integer icon) { + String existingLabel = null; + Integer existingIcon = null; + + if (componentLabelIconOverrideMap != null) { + Pair pair = componentLabelIconOverrideMap.get(component); + if (pair != null) { + existingLabel = pair.first; + existingIcon = pair.second; + } + } + + boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel) + || !Objects.equals(existingIcon, icon); + + if (changed) { + if (nonLocalizedLabel == null && icon == null) { + componentLabelIconOverrideMap.remove(component); + if (componentLabelIconOverrideMap.isEmpty()) { + componentLabelIconOverrideMap = null; + } + } else { + if (componentLabelIconOverrideMap == null) { + componentLabelIconOverrideMap = new ArrayMap<>(1); + } + + componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon)); + } + } + + return changed; + } + + /** + * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName, + * String, Integer)}. + * + * This is done when the package is updated as the components and resource IDs may have changed. + */ + public void resetOverrideComponentLabelIcon() { + componentLabelIconOverrideMap = null; + } + + @Nullable + public Pair getOverrideLabelIconForComponent(ComponentName componentName) { + if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) { + return null; + } + + return componentLabelIconOverrideMap.get(componentName); + } + /** * Test if this package is installed. */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4c4b7e6202f98..bba2f1fcbe38b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1910,6 +1910,9 @@ com.android.gallery + + + true diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 42718cba0fc99..ec8058235912e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3961,4 +3961,6 @@ + + diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index f497f114c05fa..f1e14331e33f8 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -54,6 +54,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; import com.android.server.pm.parsing.PackageInfoUtils; @@ -207,33 +208,57 @@ public class ComponentResolver { } /** Returns the given activity */ - ParsedActivity getActivity(ComponentName component) { + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public ParsedActivity getActivity(@NonNull ComponentName component) { synchronized (mLock) { return mActivities.mActivities.get(component); } } /** Returns the given provider */ - ParsedProvider getProvider(ComponentName component) { + @Nullable + ParsedProvider getProvider(@NonNull ComponentName component) { synchronized (mLock) { return mProviders.mProviders.get(component); } } /** Returns the given receiver */ - ParsedActivity getReceiver(ComponentName component) { + @Nullable + ParsedActivity getReceiver(@NonNull ComponentName component) { synchronized (mLock) { return mReceivers.mActivities.get(component); } } /** Returns the given service */ - ParsedService getService(ComponentName component) { + @Nullable + ParsedService getService(@NonNull ComponentName component) { synchronized (mLock) { return mServices.mServices.get(component); } } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean componentExists(@NonNull ComponentName componentName) { + synchronized (mLock) { + ParsedMainComponent component = mActivities.mActivities.get(componentName); + if (component != null) { + return true; + } + component = mReceivers.mActivities.get(componentName); + if (component != null) { + return true; + } + component = mServices.mServices.get(componentName); + if (component != null) { + return true; + } + return mProviders.mProviders.get(componentName) != null; + } + } + @Nullable List queryActivities(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int userId) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0965878b23dd2..8f63131e13276 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5301,7 +5301,7 @@ public class PackageManagerService extends IPackageManager.Stub AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) { - PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); + PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); if (ps == null) return null; if (shouldFilterApplicationLocked( ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) { @@ -15742,6 +15742,12 @@ public class PackageManagerService extends IPackageManager.Stub // these install state changes will be persisted in the // upcoming call to mSettings.writeLPr(). } + + if (allUsers != null) { + for (int currentUserId : allUsers) { + ps.resetOverrideComponentLabelIcon(currentUserId); + } + } } // Retrieve the overlays for shared libraries of the package. @@ -20309,6 +20315,86 @@ public class PackageManagerService extends IPackageManager.Stub } } + @Override + public void overrideLabelAndIcon(@NonNull ComponentName componentName, + @NonNull String nonLocalizedLabel, int icon, int userId) { + if (TextUtils.isEmpty(nonLocalizedLabel)) { + throw new IllegalArgumentException("Override label should be a valid String"); + } + updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId); + } + + @Override + public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) { + updateComponentLabelIcon(componentName, null, null, userId); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public void updateComponentLabelIcon(/*@NonNull*/ ComponentName componentName, + @Nullable String nonLocalizedLabel, @Nullable Integer icon, int userId) { + if (componentName == null) { + throw new IllegalArgumentException("Must specify a component"); + } + + boolean componentExists = mComponentResolver.componentExists(componentName); + if (!componentExists) { + throw new IllegalArgumentException("Component " + componentName + " not found"); + } + + int callingUid = Binder.getCallingUid(); + + String componentPkgName = componentName.getPackageName(); + int componentUid = getPackageUid(componentPkgName, 0, userId); + if (!UserHandle.isSameApp(callingUid, componentUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " does not match the target UID"); + } + + String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage); + if (TextUtils.isEmpty(allowedCallerPkg)) { + throw new SecurityException( + "There is no package defined as allowed to change a component's label or icon"); + } + + int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY, + userId); + if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " is not allowed to change a component's label or icon"); + } + + synchronized (mLock) { + AndroidPackage pkg = mPackages.get(componentPkgName); + PackageSetting pkgSetting = getPackageSetting(componentPkgName); + if (pkg == null || pkgSetting == null + || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) { + throw new SecurityException( + "Changing the label is not allowed for " + componentName); + } + + if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel, + icon, userId)) { + // Nothing changed + return; + } + } + + ArrayList components = mPendingBroadcasts.get(userId, componentPkgName); + if (components == null) { + components = new ArrayList<>(); + mPendingBroadcasts.put(userId, componentPkgName, components); + } + + String className = componentName.getClassName(); + if (!components.contains(className)) { + components.add(className); + } + + if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { + mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY); + } + } + @Override public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9a8692d029e04..8eb391454df8d 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -26,6 +26,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.pkg.PackageStateUnserialized; @@ -68,7 +69,8 @@ public class PackageSetting extends PackageSettingBase { @NonNull private PackageStateUnserialized pkgState = new PackageStateUnserialized(); - PackageSetting(String name, String realName, File codePath, File resourcePath, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public PackageSetting(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, long pVersionCode, int pkgFlags, int privateFlags, diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 7cb3df5a03504..00a5fe7665937 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -21,6 +21,9 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; @@ -697,6 +700,26 @@ public abstract class PackageSettingBase extends SettingBase { return userState.harmfulAppWarning; } + /** + * @see PackageUserState#overrideLabelAndIcon(ComponentName, String, Integer) + * + * @param userId the specific user to change the label/icon for + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component, + @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) { + return modifyUserState(userId).overrideLabelAndIcon(component, label, icon); + } + + /** + * @see PackageUserState#resetOverrideComponentLabelIcon() + * + * @param userId the specific user to reset + */ + public void resetOverrideComponentLabelIcon(@UserIdInt int userId) { + modifyUserState(userId).resetOverrideComponentLabelIcon(); + } + protected PackageSettingBase updateFrom(PackageSettingBase other) { super.copyFrom(other); this.codePath = other.codePath; diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index ec9746dabcebb..3e2ab05e83ecf 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -18,9 +18,11 @@ package com.android.server.pm; import android.content.pm.ApplicationInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.permission.PermissionsState; -abstract class SettingBase { +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public abstract class SettingBase { int pkgFlags; int pkgPrivateFlags; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f6ca87df482ff..091535dfc792a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -91,6 +91,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -418,6 +419,21 @@ public final class Settings { /** Settings and other information about permissions */ final PermissionSettings mPermissions; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public Settings(Map pkgSettings) { + mLock = new Object(); + mPackages.putAll(pkgSettings); + mSystemDir = null; + mPermissions = null; + mRuntimePermissionsPersistence = null; + mSettingsFilename = null; + mBackupSettingsFilename = null; + mPackageListFilename = null; + mStoppedPackagesFilename = null; + mBackupStoppedPackagesFilename = null; + mKernelMappingFilename = null; + } + Settings(File dataDir, PermissionSettings permission, Object lock) { mLock = lock; @@ -4328,8 +4344,9 @@ public final class Settings { return userState.isMatch(componentInfo, flags); } - boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, int flags, - int userId) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, + int flags, int userId) { final PackageSetting ps = mPackages.get(component.getPackageName()); if (ps == null) return false; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 5a1e8e2661b8d..137e0aa831d66 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -48,6 +48,7 @@ import android.content.pm.parsing.component.ParsedService; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import com.android.internal.util.ArrayUtils; @@ -271,7 +272,7 @@ public class PackageInfoUtils { ActivityInfo info = PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(a, applicationInfo); - assignSharedFieldsForComponentInfo(info, a, pkgSetting); + assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId); return info; } @@ -306,7 +307,7 @@ public class PackageInfoUtils { ServiceInfo info = PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(s, applicationInfo); - assignSharedFieldsForComponentInfo(info, s, pkgSetting); + assignSharedFieldsForComponentInfo(info, s, pkgSetting, userId); return info; } @@ -333,7 +334,7 @@ public class PackageInfoUtils { } ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(p, flags, applicationInfo); - assignSharedFieldsForComponentInfo(info, p, pkgSetting); + assignSharedFieldsForComponentInfo(info, p, pkgSetting, userId); return info; } @@ -358,7 +359,7 @@ public class PackageInfoUtils { info.nativeLibraryDir = pkg.getNativeLibraryDir(); info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir(); - assignStateFieldsForPackageItemInfo(info, i, pkgSetting); + assignStateFieldsForPackageItemInfo(info, i, pkgSetting, userId); return info; } @@ -426,8 +427,9 @@ public class PackageInfoUtils { } private static void assignSharedFieldsForComponentInfo(@NonNull ComponentInfo componentInfo, - @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting) { - assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting); + @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting, + int userId) { + assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting, userId); componentInfo.descriptionRes = mainComponent.getDescriptionRes(); componentInfo.directBootAware = mainComponent.isDirectBootAware(); componentInfo.enabled = mainComponent.isEnabled(); @@ -436,8 +438,12 @@ public class PackageInfoUtils { private static void assignStateFieldsForPackageItemInfo( @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component, - @Nullable PackageSetting pkgSetting) { - // TODO(b/135203078): Add setting related state + @Nullable PackageSetting pkgSetting, int userId) { + Pair labelAndIcon = + ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting, + userId); + packageItemInfo.nonLocalizedLabel = labelAndIcon.first; + packageItemInfo.icon = labelAndIcon.second; } @CheckResult diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java new file mode 100644 index 0000000000000..54466ac8f26b2 --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.server.pm.parsing; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.parsing.component.ParsedComponent; +import android.util.Pair; + +import com.android.server.pm.PackageSetting; + +/** + * For exposing internal fields to the rest of the server, enforcing that any overridden state from + * a {@link com.android.server.pm.PackageSetting} is applied. + * + * TODO(chiuwinson): The fields on ParsedComponent are not actually hidden. Will need to find a + * way to enforce the mechanism now that they exist in core instead of server. Can't rely on + * package-private. + * + * @hide + */ +public class ParsedComponentStateUtils { + + @NonNull + public static Pair getNonLocalizedLabelAndIcon(ParsedComponent component, + @Nullable PackageSetting pkgSetting, int userId) { + CharSequence label = component.getNonLocalizedLabel(); + int icon = component.getIcon(); + + Pair overrideLabelIcon = pkgSetting == null ? null : + pkgSetting.readUserState(userId) + .getOverrideLabelIconForComponent(component.getComponentName()); + if (overrideLabelIcon != null) { + if (overrideLabelIcon.first != null) { + label = overrideLabelIcon.first; + } + if (overrideLabelIcon.second != null) { + icon = overrideLabelIcon.second; + } + } + + return Pair.create(label, icon); + } +} diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp new file mode 100644 index 0000000000000..a2668a184fe06 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2019 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. +// + +// NOTE: This test is separate from service tests since it relies on same vs different calling UID, +// and this is more representative of a real caller. It also uses Mockito extended, and this +// prevents converting the entire services test module. +android_test { + name: "PackageManagerComponentOverrideTests", + srcs: [ + "src/**/*.kt" + ], + static_libs: [ + "androidx.test.runner", + "mockito-target-extended-minus-junit4", + "services.core", + "servicestests-utils-mockito-extended", + "testng", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows + "truth-prebuilt", + ], + + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: ["device-tests"], + platform_apis: true, +} diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml new file mode 100644 index 0000000000000..c25e1122c730d --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml new file mode 100644 index 0000000000000..b83b1a8fb1133 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml @@ -0,0 +1,29 @@ + + + + diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..86b12fca81cc617f7bdf1aa4474b44659d35ddd3 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4FVdQ I&MBb@07S4n@&Et; literal 0 HcmV?d00001 diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..49dbb4fd7a46363bf11f923a23673157c3fe13c3 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4 + + + + + + diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt new file mode 100644 index 0000000000000..ecdb30f5e84b6 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2020 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.server.pm.test.override + +import android.content.ComponentName +import android.content.Context +import android.content.pm.parsing.component.ParsedActivity +import android.os.Binder +import android.os.UserHandle +import android.util.ArrayMap +import com.android.server.pm.AppsFilter +import com.android.server.pm.ComponentResolver +import com.android.server.pm.PackageManagerService +import com.android.server.pm.PackageSetting +import com.android.server.pm.Settings +import com.android.server.pm.UserManagerService +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.parsing.pkg.ParsedPackage +import com.android.server.pm.permission.PermissionManagerServiceInternal +import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType +import com.android.server.pm.test.override.R +import com.android.server.testutils.TestHandler +import com.android.server.testutils.mock +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.android.server.wm.ActivityTaskManagerInternal +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.intThat +import org.mockito.Mockito.never +import org.mockito.Mockito.same +import org.mockito.Mockito.verify +import org.testng.Assert.assertThrows +import java.io.File + +@RunWith(Parameterized::class) +class PackageManagerComponentLabelIconOverrideTest { + + companion object { + private const val VALID_PKG = "com.android.server.pm.test.override" + private const val SHARED_PKG = "com.android.server.pm.test.override.shared" + private const val INVALID_PKG = "com.android.server.pm.test.override.invalid" + + private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST + + private const val DEFAULT_LABEL = "DefaultLabel" + private const val TEST_LABEL = "TestLabel" + + private const val DEFAULT_ICON = R.drawable.black16x16 + private const val TEST_ICON = R.drawable.white16x16 + + private const val COMPONENT_CLASS_NAME = ".TestComponent" + + sealed class Result { + // Component label/icon changed, message sent to send broadcast + object Changed : Result() + + // Component label/icon changed, message was pending, not re-sent + object ChangedWithoutNotify : Result() + + // Component label/icon did not changed, was already equivalent + object NotChanged : Result() + + // Updating label/icon encountered a specific exception + data class Exception(val type: Class) : Result() + } + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun parameters() = arrayOf( + // Start with an array of the simplest known inputs and expected outputs + Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed), + Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed), + Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java) + ) + .flatMap { param -> + mutableListOf(param).apply { + if (param.result is Result.Changed) { + // For each param that would've succeeded, also verify that if a change + // happened, but a message was pending, another is not re-queued/reset + this += param.copy(result = Result.ChangedWithoutNotify) + // Also verify that when the component is already configured, no change + // is propagated + this += param.copy(result = Result.NotChanged) + } + // For all params, verify that an invalid component will cause an + // IllegalArgumentException, instead of result initially specified + this += param.copy(componentName = null, + result = Result.Exception(IllegalArgumentException::class.java)) + // Also verify an updated system app variant, which should have the same + // result as a vanilla system app + this += param.copy(appType = AppType.UPDATED_SYSTEM_APP) + // Also verify a non-system app will cause a failure, since normal apps + // are not allowed to edit their label/icon + this += param.copy(appType = AppType.NORMAL_APP, + result = Result.Exception(SecurityException::class.java)) + } + } + + data class Params( + val pkgName: String, + private val appType: AppType, + val result: Result, + val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME) + ) { + constructor(pkgName: String, appType: AppType, exception: Class) + : this(pkgName, appType, Result.Exception(exception)) + + val expectedLabel = when (result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL + is Result.Exception -> DEFAULT_LABEL + } + + val expectedIcon = when (result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_ICON + is Result.Exception -> DEFAULT_ICON + } + + val isUpdatedSystemApp = appType == AppType.UPDATED_SYSTEM_APP + val isSystem = appType == AppType.SYSTEM_APP || isUpdatedSystemApp + + override fun toString(): String { + val resultString = when (result) { + Result.Changed -> "Changed" + Result.ChangedWithoutNotify -> "ChangedWithoutNotify" + Result.NotChanged -> "NotChanged" + is Result.Exception -> result.type.simpleName + } + + // Nicer formatting for the test method suffix + return "pkg=$pkgName, type=$appType, component=$componentName, result=$resultString" + } + + enum class AppType { SYSTEM_APP, UPDATED_SYSTEM_APP, NORMAL_APP } + } + } + + @Parameterized.Parameter(0) + lateinit var params: Params + + private lateinit var testHandler: TestHandler + private lateinit var mockPendingBroadcasts: PackageManagerService.PendingPackageBroadcasts + private lateinit var mockPkg: AndroidPackage + private lateinit var mockPkgSetting: PackageSetting + private lateinit var service: PackageManagerService + + private val userId = UserHandle.getCallingUserId() + private val userIdDifferent = userId + 1 + + @Before + fun setUpMocks() { + makeTestData() + + testHandler = TestHandler(null) + if (params.result is Result.ChangedWithoutNotify) { + // Case where the handler already has a message and so another should not be sent. + // This case will verify that only 1 message exists, which is the one added here. + testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST) + } + + mockPendingBroadcasts = PackageManagerService.PendingPackageBroadcasts() + + service = mockService() + } + + @Test + fun updateComponentLabelIcon() { + fun runUpdate() { + service.updateComponentLabelIcon(params.componentName, TEST_LABEL, TEST_ICON, userId) + } + + when (val result = params.result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> { + runUpdate() + verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!, + TEST_LABEL, TEST_ICON, userId) + } + is Result.Exception -> { + assertThrows(result.type) { runUpdate() } + verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon( + any(), any(), anyInt(), anyInt()) + } + } + } + + @After + fun verifyExpectedResult() { + if (params.componentName != null) { + val activityInfo = service.getActivityInfo(params.componentName, 0, userId) + assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel) + assertThat(activityInfo.icon).isEqualTo(params.expectedIcon) + } + } + + @After + fun verifyDifferentUserUnchanged() { + when (params.result) { + Result.Changed, Result.ChangedWithoutNotify -> { + val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent) + assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL) + assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON) + } + Result.NotChanged, is Result.Exception -> {} + }.run { /*exhaust*/ } + } + + @After + fun verifyHandlerHasMessage() { + when (params.result) { + is Result.Changed, is Result.ChangedWithoutNotify -> { + assertThat(testHandler.pendingMessages).hasSize(1) + assertThat(testHandler.pendingMessages.first().message.what) + .isEqualTo(SEND_PENDING_BROADCAST) + } + is Result.NotChanged, is Result.Exception -> { + assertThat(testHandler.pendingMessages).hasSize(0) + } + }.run { /*exhaust*/ } + } + + @After + fun verifyPendingBroadcast() { + when (params.result) { + is Result.Changed, Result.ChangedWithoutNotify -> { + assertThat(mockPendingBroadcasts.get(userId, params.pkgName)) + .containsExactly(params.componentName!!.className) + .inOrder() + } + is Result.NotChanged, is Result.Exception -> { + assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull() + } + }.run { /*exhaust*/ } + } + + private fun makePkg(pkgName: String, block: ParsedPackage.() -> Unit = {}) = + PackageImpl.forTesting(pkgName) + .setEnabled(true) + .let { it.hideAsParsed() as ParsedPackage } + .setSystem(params.isSystem) + .apply(block) + .hideAsFinal() + + private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"), + File("/test"), null, null, null, null, 0, 0, 0, 0, null, null, null)) { + this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp + } + + private fun makeTestData() { + mockPkg = makePkg(params.pkgName) + mockPkgSetting = makePkgSetting(params.pkgName) + + if (params.result is Result.NotChanged) { + // If verifying no-op behavior, set the current setting to the test values + mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL, + TEST_ICON, userId) + // Then clear the mock because the line above just incremented it + clearInvocations(mockPkgSetting) + } + } + + private fun mockService(): PackageManagerService { + val mockedPkgs = mapOf( + // Must use the test app's UID so that PMS can match them when querying, since + // the static Binder.getCallingUid can't mocked as it's marked final + VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() }, + SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() }, + INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 } + ) + val mockedPkgSettings = mapOf( + VALID_PKG to makePkgSetting(VALID_PKG), + SHARED_PKG to makePkgSetting(SHARED_PKG), + INVALID_PKG to makePkgSetting(INVALID_PKG) + ) + // Add pkgSetting under test so its attributes override the defaults added above + .plus(params.pkgName to mockPkgSetting) + + val mockActivity: ParsedActivity = mock { + whenever(this.packageName) { params.pkgName } + whenever(this.nonLocalizedLabel) { DEFAULT_LABEL } + whenever(this.icon) { DEFAULT_ICON } + whenever(this.componentName) { params.componentName } + whenever(this.name) { params.componentName?.className } + whenever(this.isEnabled) { true } + whenever(this.isDirectBootAware) { params.isSystem } + } + + val mockSettings = Settings(mockedPkgSettings) + val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked { + params.componentName?.let { + whenever(this.componentExists(same(it))) { true } + whenever(this.getActivity(same(it))) { mockActivity } + } + } + val mockUserManagerService: UserManagerService = mockThrowOnUnmocked { + val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent } + whenever(this.exists(intThat(matcher))) { true } + whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true } + } + val mockPermissionManagerService: PermissionManagerServiceInternal = mockThrowOnUnmocked { + whenever(this.enforceCrossUserPermission(anyInt(), anyInt(), anyBoolean(), anyBoolean(), + anyString())) { } + } + val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked { + whenever(this.isCallerRecents(anyInt())) { false } + } + val mockAppsFilter: AppsFilter = mockThrowOnUnmocked { + whenever(this.shouldFilterApplication(anyInt(), any(), + any(), anyInt())) { false } + } + val mockContext: Context = mockThrowOnUnmocked { + whenever(this.getString( + com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG } + } + val mockInjector: PackageManagerService.Injector = mock { + whenever(this.lock) { Object() } + whenever(this.componentResolver) { mockComponentResolver } + whenever(this.userManagerService) { mockUserManagerService } + whenever(this.permissionManagerServiceInternal) { mockPermissionManagerService } + whenever(this.settings) { mockSettings } + whenever(this.activityTaskManagerInternal) { mockActivityTaskManager } + whenever(this.appsFilter) { mockAppsFilter } + whenever(this.context) { mockContext } + } + val testParams = PackageManagerService.TestParams().apply { + this.handler = testHandler + this.pendingPackageBroadcasts = mockPendingBroadcasts + this.resolveComponentName = ComponentName("android", ".Test") + this.packages = ArrayMap().apply { putAll(mockedPkgs) } + } + + return PackageManagerService(mockInjector, testParams) + } +} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e58e911799313..b457856e86308 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -107,6 +107,8 @@ java_library { name: "servicestests-utils", srcs: [ "utils/**/*.java", + "utils/**/*.kt", + "utils-mockito/**/*.kt", ], static_libs: [ "junit", @@ -117,6 +119,22 @@ java_library { ], } +java_library { + name: "servicestests-utils-mockito-extended", + srcs: [ + "utils/**/*.java", + "utils/**/*.kt", + "utils-mockito/**/*.kt", + ], + static_libs: [ + "junit", + "mockito-target-extended-minus-junit4", + ], + libs: [ + "android.test.runner", + ], +} + filegroup { name: "servicestests-SuspendTestApp-files", srcs: [ diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index 063cd5dacc93d..78c708084d382 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -18,6 +18,8 @@ package com.android.server.om import android.net.Uri import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index f532dd87909e1..d7b02f44ed26a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -31,12 +31,12 @@ import android.os.Debug import android.os.Environment import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.om.mockThrowOnUnmocked -import com.android.server.om.whenever import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageSetting import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateUnserialized +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever import org.junit.BeforeClass import org.mockito.Mockito import org.mockito.Mockito.anyInt diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt similarity index 75% rename from services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt rename to services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt index 0f915dbdcf6fd..056fa886f6402 100644 --- a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.om +package com.android.server.testutils import org.mockito.Answers import org.mockito.Mockito @@ -31,6 +31,13 @@ object MockitoUtils { else -> { val arguments = it.arguments ?.takeUnless { it.isEmpty() } + ?.mapIndexed { index, arg -> + try { + arg?.toString() + } catch (e: Exception) { + "toString[$index] threw ${e.message}" + } + } ?.joinToString() ?.let { "with $it" @@ -46,6 +53,8 @@ object MockitoUtils { inline fun mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block) +fun spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block) + fun Stubber.whenever(mock: Type) = Mockito.`when`(mock) fun whenever(mock: Type) = Mockito.`when`(mock) @@ -55,7 +64,7 @@ fun whenever(mock: Type, block: InvocationOnMock.() -> Any?) = fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { } -inline fun mockThrowOnUnmocked(block: T.() -> Unit): T { +inline fun spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T { val swappingAnswer = object : Answer { var delegate: Answer<*> = Answers.RETURNS_DEFAULTS @@ -64,9 +73,12 @@ inline fun mockThrowOnUnmocked(block: T.() -> Unit): T { } } - return Mockito.mock(T::class.java, swappingAnswer).apply(block) + return Mockito.mock(T::class.java, Mockito.withSettings().spiedInstance(value) + .defaultAnswer(swappingAnswer)).apply(block) .also { // To allow when() usage inside block, only swap to throwing afterwards swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS } } + +inline fun mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked(null, block) diff --git a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java index 69db38438609c..355e7f333bb77 100644 --- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java +++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java @@ -139,7 +139,7 @@ public class TestHandler extends Handler { } } - private class MsgInfo implements Comparable { + public class MsgInfo implements Comparable { public final Message message; public final long sendTime; public final RuntimeException postPoint; From 5cda4548108894d175ec6eaa578d193501bd6ae3 Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 1 Apr 2020 11:27:30 -0700 Subject: [PATCH 3/3] Add mPackageParserCallback assignment in TestParams constructor This is a fix for a revert of a revert at change ID I4f5247f528f62c175bd34f0e4cb16c9456c8afe4. It conflicts with Id53a2e65f6e5e4dee9a41cc77007275b3a220ac3, which was merged before. That change moved the final callback field to constructor assignment, so this fix does the same for the test constructor. Bug: 113638339 Test: build and atest PackageManagerComponentOverrideTests Change-Id: Ia2c456335ad490ff3d555ed735edf3ae702ef836 --- .../core/java/com/android/server/pm/PackageManagerService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8f63131e13276..f96ab1d9a0427 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1065,6 +1065,7 @@ public class PackageManagerService extends IPackageManager.Stub public boolean onlyCore; public OverlayConfig overlayConfig; public PackageDexOptimizer packageDexOptimizer; + public PackageParser2.Callback packageParserCallback; public IPermissionManager permissionManagerService; public PendingPackageBroadcasts pendingPackageBroadcasts; public PackageManagerInternal pmInternal; @@ -2779,6 +2780,7 @@ public class PackageManagerService extends IPackageManager.Stub mOnlyCore = testParams.onlyCore; mOverlayConfig = testParams.overlayConfig; mPackageDexOptimizer = testParams.packageDexOptimizer; + mPackageParserCallback = testParams.packageParserCallback; mPendingBroadcasts = testParams.pendingPackageBroadcasts; mPermissionManagerService = testParams.permissionManagerService; mPmInternal = testParams.pmInternal;