summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chiu <chiuwinson@google.com> 2020-04-01 18:20:51 +0000
committer Winson Chiu <chiuwinson@google.com> 2020-04-01 19:17:53 +0000
commit8e865ecd066d05bdb1ff23f3337b39c8a558555f (patch)
treebb0aa7e6909dcd4c7e3126e960b12047b8134012
parent1867bc81e2551f71c6f5e2d335c5edc664a4186c (diff)
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
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl29
-rw-r--r--core/java/android/content/pm/PackageUserState.java71
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--services/core/java/com/android/server/pm/ComponentResolver.java33
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java88
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java23
-rw-r--r--services/core/java/com/android/server/pm/SettingBase.java4
-rw-r--r--services/core/java/com/android/server/pm/Settings.java21
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java22
-rw-r--r--services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java58
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/Android.bp40
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml34
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml29
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.pngbin0 -> 260 bytes
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.pngbin0 -> 262 bytes
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/res/values/values.xml21
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt358
-rw-r--r--services/tests/servicestests/Android.bp18
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt4
-rw-r--r--services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt (renamed from services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt)20
-rw-r--r--services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java2
24 files changed, 862 insertions, 24 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5bad055810cc..8bebafff37f0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -306,6 +306,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}.
*/
@UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 61b1553e28a8..327d1b8beeb1 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<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
private String[] cachedOverlayPaths;
+ @Nullable
+ private ArrayMap<ComponentName, Pair<String, Integer>> 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() {
@@ -147,6 +159,65 @@ public class PackageUserState {
}
/**
+ * 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<String, Integer> 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<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
+ if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) {
+ return null;
+ }
+
+ return componentLabelIconOverrideMap.get(componentName);
+ }
+
+ /**
* Test if this package is installed.
*/
public boolean isAvailable(int flags) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4c4b7e6202f9..bba2f1fcbe38 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1910,6 +1910,9 @@
<!-- The name of the package that will hold the system gallery role. -->
<string name="config_systemGallery" translatable="false">com.android.gallery</string>
+ <!-- The name of the package that will be allowed to change its components' label/icon. -->
+ <string name="config_overrideComponentUiPackage" translatable="false"></string>
+
<!-- Enable/disable default bluetooth profiles:
HSP_AG, ObexObjectPush, Audio, NAP -->
<bool name="config_bluetooth_default_profiles">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 42718cba0fc9..ec8058235912 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3961,4 +3961,6 @@
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<java-symbol type="bool" name="config_assistantOnTopOfDream"/>
+
+ <java-symbol type="string" name="config_overrideComponentUiPackage" />
</resources>
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index f497f114c05f..f1e14331e33f 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<ResolveInfo> 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 0965878b23dd..8f63131e1327 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.
@@ -20310,6 +20316,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<String> 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) {
if (!mUserManager.exists(userId)) return;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9a8692d029e0..8eb391454df8 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 7cb3df5a0350..00a5fe766593 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 ec9746dabceb..3e2ab05e83ec 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 f6ca87df482f..091535dfc792 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<String, PackageSetting> 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 5a1e8e2661b8..137e0aa831d6 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<CharSequence, Integer> 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 000000000000..54466ac8f26b
--- /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<CharSequence, Integer> getNonLocalizedLabelAndIcon(ParsedComponent component,
+ @Nullable PackageSetting pkgSetting, int userId) {
+ CharSequence label = component.getNonLocalizedLabel();
+ int icon = component.getIcon();
+
+ Pair<String, Integer> 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 000000000000..a2668a184fe0
--- /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 000000000000..c25e1122c730
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.override">
+
+ <uses-sdk
+ android:minSdkVersion="1"
+ android:targetSdkVersion="28" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.mock" android:required="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="PackageManagerComponentOverrideTests"
+ android:targetPackage="com.android.server.pm.test.override" />
+
+</manifest>
diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
new file mode 100644
index 000000000000..b83b1a8fb113
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<configuration description="Test module config for PackageManagerComponentOverrideTests">
+ <option name="test-tag" value="PackageManagerComponentOverrideTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageManagerComponentOverrideTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.pm.test.override" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
new file mode 100644
index 000000000000..86b12fca81cc
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
Binary files differ
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
new file mode 100644
index 000000000000..49dbb4fd7a46
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
Binary files differ
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
new file mode 100644
index 000000000000..73d11281c96f
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <public name="black16x16" type="drawable" id="0x7f080001"/>
+ <public name="white16x16" type="drawable" id="0x7f080002"/>
+</resources>
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 000000000000..ecdb30f5e84b
--- /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<out java.lang.Exception>) : 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<out Exception>)
+ : 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<ComponentName>(), 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<PackageSetting>(),
+ any<PackageSetting>(), 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<String, AndroidPackage>().apply { putAll(mockedPkgs) }
+ }
+
+ return PackageManagerService(mockInjector, testParams)
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e58e91179931..b457856e8630 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 063cd5dacc93..78c708084d38 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 f532dd87909e..d7b02f44ed26 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
index 0f915dbdcf6f..056fa886f640 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 <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+fun <T> spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block)
+
fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
@@ -55,7 +64,7 @@ fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
-inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T {
val swappingAnswer = object : Answer<Any?> {
var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
@@ -64,9 +73,12 @@ inline fun <reified T> 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 <reified T> mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked<T>(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 69db38438609..355e7f333bb7 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<MsgInfo> {
+ public class MsgInfo implements Comparable<MsgInfo> {
public final Message message;
public final long sendTime;
public final RuntimeException postPoint;