Component Alias initial (rudimentary) prototype
See go/component-alias-prototype-overview for what it does.
This is disabled by default. Enable it via device config.
Test: atest ComponentAliasTests
Bug: 196254758
Change-Id: I2d177f6a475d878abbde55c3642c09da99dfea30
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 846349d..ce9c2ef 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6993,6 +6993,7 @@
private int mContentUserHint = UserHandle.USER_CURRENT;
/** Token to track instant app launches. Local only; do not copy cross-process. */
private String mLaunchToken;
+ private Intent mOriginalIntent; // Used for the experimental "component alias" feature.
// ---------------------------------------------------------------------
@@ -7029,6 +7030,7 @@
this.mIdentifier = o.mIdentifier;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
+ this.mOriginalIntent = o.mOriginalIntent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
@@ -8193,6 +8195,22 @@
return mType;
}
+
+ /**
+ * @hide For the experimental component alias feature. Do not use, unless you know what it is.
+ */
+ @Nullable
+ public Intent getOriginalIntent() {
+ return mOriginalIntent;
+ }
+
+ /**
+ * @hide For the experimental component alias feature. Do not use, unless you know what it is.
+ */
+ public void setOriginalIntent(@Nullable Intent originalIntent) {
+ mOriginalIntent = originalIntent;
+ }
+
/**
* Return the MIME data type of this intent. If the type field is
* explicitly set, that is simply returned. Otherwise, if the data is set,
@@ -10838,6 +10856,11 @@
mSelector.toShortString(b, secure, comp, extras, clip);
b.append("}");
}
+ if (mOriginalIntent != null) {
+ b.append(" org={");
+ mOriginalIntent.toShortString(b, secure, comp, extras, clip);
+ b.append("}");
+ }
}
/** @hide */
@@ -11133,6 +11156,13 @@
}
out.writeInt(mContentUserHint);
out.writeBundle(mExtras);
+
+ if (mOriginalIntent != null) {
+ out.writeInt(1);
+ mOriginalIntent.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<Intent> CREATOR
@@ -11186,6 +11216,9 @@
}
mContentUserHint = in.readInt();
mExtras = in.readBundle();
+ if (in.readInt() != 0) {
+ mOriginalIntent = new Intent(in);
+ }
}
/**
@@ -11410,6 +11443,9 @@
if (mClipData != null) {
mClipData.prepareToLeaveProcess(leavingPackage, getFlags());
}
+ if (mOriginalIntent != null) {
+ mOriginalIntent.prepareToLeaveProcess(leavingPackage);
+ }
if (mExtras != null && !mExtras.isParcelled()) {
final Object intent = mExtras.get(Intent.EXTRA_INTENT);
@@ -11505,6 +11541,9 @@
if (mClipData != null) {
mClipData.prepareToEnterProcess(source);
}
+ if (mOriginalIntent != null) {
+ mOriginalIntent.prepareToEnterProcess(false, source);
+ }
if (mContentUserHint != UserHandle.USER_CURRENT) {
if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e4c0765..49722d0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -816,8 +816,19 @@
return null;
}
- return startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
+ // If what the client try to start/connect was an alias, then we need to return the
+ // alias component name to the client, not the "target" component name, which is
+ // what realResult contains.
+ final ComponentName realResult =
+ startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
allowBackgroundActivityStarts, backgroundActivityStartsToken);
+ if (res.aliasComponent != null
+ && !realResult.getPackageName().startsWith("!")
+ && !realResult.getPackageName().startsWith("?")) {
+ return res.aliasComponent;
+ } else {
+ return realResult;
+ }
}
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
@@ -2838,7 +2849,7 @@
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
ConnectionRecord c = new ConnectionRecord(b, activity,
connection, flags, clientLabel, clientIntent,
- callerApp.uid, callerApp.processName, callingPackage);
+ callerApp.uid, callerApp.processName, callingPackage, res.aliasComponent);
IBinder binder = connection.asBinder();
s.addConnection(binder, c);
@@ -2915,8 +2926,13 @@
if (s.app != null && b.intent.received) {
// Service is already running, so we can immediately
// publish the connection.
+
+ // If what the client try to start/connect was an alias, then we need to
+ // pass the alias component name instead to the client.
+ final ComponentName clientSideComponentName =
+ res.aliasComponent != null ? res.aliasComponent : s.name;
try {
- c.conn.connected(s.name, b.intent.binder, false);
+ c.conn.connected(clientSideComponentName, b.intent.binder, false);
} catch (Exception e) {
Slog.w(TAG, "Failure sending service " + s.shortInstanceName
+ " to connection " + c.conn.asBinder()
@@ -2987,8 +3003,12 @@
continue;
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
+ // If what the client try to start/connect was an alias, then we need to
+ // pass the alias component name instead to the client.
+ final ComponentName clientSideComponentName =
+ c.aliasComponent != null ? c.aliasComponent : r.name;
try {
- c.conn.connected(r.name, service, false);
+ c.conn.connected(clientSideComponentName, service, false);
} catch (Exception e) {
Slog.w(TAG, "Failure sending service " + r.shortInstanceName
+ " to connection " + c.conn.asBinder()
@@ -3140,9 +3160,22 @@
final ServiceRecord record;
final String permission;
- ServiceLookupResult(ServiceRecord _record, String _permission) {
+ /**
+ * Set only when we looked up to this service via an alias. Otherwise, it's null.
+ */
+ @Nullable
+ final ComponentName aliasComponent;
+
+ ServiceLookupResult(ServiceRecord _record, ComponentName _aliasComponent) {
record = _record;
+ permission = null;
+ aliasComponent = _aliasComponent;
+ }
+
+ ServiceLookupResult(String _permission) {
+ record = null;
permission = _permission;
+ aliasComponent = null;
}
}
@@ -3174,10 +3207,19 @@
/* name= */ "service", callingPackage);
ServiceMap smap = getServiceMapLocked(userId);
+
+ // See if the intent refers to an alias. If so, update the intent with the target component
+ // name. `resolution` will contain the alias component name, which we need to return
+ // to the client.
+ final ComponentAliasResolver.Resolution resolution =
+ mAm.mComponentAliasResolver.resolveService(service, resolvedType,
+ /* match flags */ 0, userId, callingUid);
+
final ComponentName comp;
if (instanceName == null) {
comp = service.getComponent();
} else {
+ // This is for isolated services
final ComponentName realComp = service.getComponent();
if (realComp == null) {
throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
@@ -3186,6 +3228,7 @@
comp = new ComponentName(realComp.getPackageName(),
realComp.getClassName() + ":" + instanceName);
}
+
if (comp != null) {
r = smap.mServicesByInstanceName.get(comp);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by component: " + r);
@@ -3243,7 +3286,7 @@
String msg = "association not allowed between packages "
+ callingPackage + " and " + name.getPackageName();
Slog.w(TAG, "Service lookup failed: " + msg);
- return new ServiceLookupResult(null, msg);
+ return new ServiceLookupResult(msg);
}
// Store the defining packageName and uid, as they might be changed in
@@ -3361,11 +3404,11 @@
String msg = "association not allowed between packages "
+ callingPackage + " and " + r.packageName;
Slog.w(TAG, "Service lookup failed: " + msg);
- return new ServiceLookupResult(null, msg);
+ return new ServiceLookupResult(msg);
}
if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
resolvedType, r.appInfo)) {
- return new ServiceLookupResult(null, "blocked by firewall");
+ return new ServiceLookupResult("blocked by firewall");
}
if (mAm.checkComponentPermission(r.permission,
callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) {
@@ -3374,14 +3417,14 @@
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " that is not exported from uid " + r.appInfo.uid);
- return new ServiceLookupResult(null, "not exported from uid "
+ return new ServiceLookupResult("not exported from uid "
+ r.appInfo.uid);
}
Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " requires " + r.permission);
- return new ServiceLookupResult(null, r.permission);
+ return new ServiceLookupResult(r.permission);
} else if (Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals(r.permission)
&& callingUid != Process.SYSTEM_UID) {
// Hotword detection must run in its own sandbox, and we don't even trust
@@ -3392,7 +3435,7 @@
+ ", uid=" + callingUid
+ " requiring permission " + r.permission
+ " can only be bound to from the system.");
- return new ServiceLookupResult(null, "can only be bound to "
+ return new ServiceLookupResult("can only be bound to "
+ "by the system.");
} else if (r.permission != null && callingPackage != null) {
final int opCode = AppOpsManager.permissionToOpCode(r.permission);
@@ -3405,7 +3448,7 @@
return null;
}
}
- return new ServiceLookupResult(r, null);
+ return new ServiceLookupResult(r, resolution.getAliasComponent());
}
return null;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 048a787..ac537fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -123,6 +123,8 @@
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
"kill_bg_restricted_cached_idle_settle_time";
+ static final String KEY_ENABLE_COMPONENT_ALIAS = "enable_experimental_component_alias";
+ static final String KEY_COMPONENT_ALIAS_OVERRIDES = "component_alias_overrides";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -197,6 +199,8 @@
* Whether or not to enable the extra delays to service restarts on memory pressure.
*/
private static final boolean DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE = true;
+ private static final boolean DEFAULT_ENABLE_COMPONENT_ALIAS = false;
+ private static final String DEFAULT_COMPONENT_ALIAS_OVERRIDES = "";
// Flag stored in the DeviceConfig API.
/**
@@ -580,6 +584,14 @@
@GuardedBy("mService")
boolean mEnableExtraServiceRestartDelayOnMemPressure =
DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE;
+ /** Whether to enable "component alias" experimental feature. */
+ volatile boolean mEnableComponentAlias = DEFAULT_ENABLE_COMPONENT_ALIAS;
+
+ /**
+ * Defines component aliases. Format
+ * ComponentName ":" ComponentName ( "," ComponentName ":" ComponentName )*
+ */
+ volatile String mComponentAliasOverrides = DEFAULT_COMPONENT_ALIAS_OVERRIDES;
private final ActivityManagerService mService;
private ContentResolver mResolver;
@@ -812,6 +824,10 @@
case KEY_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE:
updateEnableExtraServiceRestartDelayOnMemPressure();
break;
+ case KEY_ENABLE_COMPONENT_ALIAS:
+ case KEY_COMPONENT_ALIAS_OVERRIDES:
+ updateComponentAliases();
+ break;
default:
break;
}
@@ -1243,6 +1259,18 @@
return def;
}
+ private void updateComponentAliases() {
+ mEnableComponentAlias = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_ENABLE_COMPONENT_ALIAS,
+ DEFAULT_ENABLE_COMPONENT_ALIAS);
+ mComponentAliasOverrides = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_COMPONENT_ALIAS_OVERRIDES,
+ DEFAULT_COMPONENT_ALIAS_OVERRIDES);
+ mService.mComponentAliasResolver.update(mEnableComponentAlias, mComponentAliasOverrides);
+ }
+
private void updateImperceptibleKillExemptions() {
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1472,6 +1500,10 @@
pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
pw.print(" "); pw.print(KEY_FGS_ALLOW_OPT_OUT);
pw.print("="); pw.println(mFgsAllowOptOut);
+ pw.print(" "); pw.print(KEY_ENABLE_COMPONENT_ALIAS);
+ pw.print("="); pw.println(mEnableComponentAlias);
+ pw.print(" "); pw.print(KEY_COMPONENT_ALIAS_OVERRIDES);
+ pw.print("="); pw.println(mComponentAliasOverrides);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7fc35d4..b202c23 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -779,6 +779,9 @@
@GuardedBy("this")
private ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+ @GuardedBy("this")
+ final ComponentAliasResolver mComponentAliasResolver;
+
/**
* Tracks association information for a particular package along with debuggability.
* <p> Associations for a package A are allowed to package B if B is part of the
@@ -2220,6 +2223,7 @@
mUseFifoUiScheduling = false;
mEnableOffloadQueue = false;
mFgBroadcastQueue = mBgBroadcastQueue = mOffloadBroadcastQueue = null;
+ mComponentAliasResolver = new ComponentAliasResolver(this);
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2346,6 +2350,7 @@
mInternal = new LocalService();
mPendingStartActivityUids = new PendingStartActivityUids(mContext);
mTraceErrorLogger = new TraceErrorLogger();
+ mComponentAliasResolver = new ComponentAliasResolver(this);
}
public void setSystemServiceManager(SystemServiceManager mgr) {
@@ -4784,6 +4789,10 @@
showConsoleNotificationIfActive();
t.traceEnd();
+
+ // Load the component aliases.
+ mComponentAliasResolver.update(
+ mConstants.mEnableComponentAlias, mConstants.mComponentAliasOverrides);
}
private void showConsoleNotificationIfActive() {
@@ -8791,6 +8800,12 @@
pw.println("-------------------------------------------------------------------------------");
}
dumpUsers(pw);
+
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
+ mComponentAliasResolver.dump(pw);
}
}
@@ -9107,6 +9122,8 @@
opti++;
}
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
+ } else if ("component-alias".equals(cmd)) {
+ mComponentAliasResolver.dump(pw);
} else {
// Dumping a single activity?
if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
new file mode 100644
index 0000000..6ed9447
--- /dev/null
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Manages and handles component aliases, which is an experimental feature.
+ *
+ * For now, this is an experimental feature to evaluate feasibility, so the implementation is
+ * "quick & dirty". For example, to define aliases, we use a regular intent filter and meta-data
+ * in the manifest, instead of adding proper tags/attributes to AndroidManifest.xml.
+ *
+ * Also, for now, aliases can be defined across any packages, but in the final version, there'll
+ * be restrictions:
+ * - We probably should only allow either privileged or preinstalled apps.
+ * - Aliases can only be defined across packages that are atomically installed, and signed with the
+ * same key.
+ */
+public class ComponentAliasResolver {
+ private static final String TAG = "ComponentAliasResolver";
+ private static final boolean DEBUG = true;
+
+ private final Object mLock = new Object();
+ private final ActivityManagerService mAm;
+ private final Context mContext;
+
+ @GuardedBy("mLock")
+ private boolean mEnabled;
+
+ @GuardedBy("mLock")
+ private String mOverrideString;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<ComponentName, ComponentName> mFromTo = new ArrayMap<>();
+
+ private static final String ALIAS_FILTER_ACTION = "android.intent.action.EXPERIMENTAL_IS_ALIAS";
+ private static final String META_DATA_ALIAS_TARGET = "alias_target";
+
+ public ComponentAliasResolver(ActivityManagerService service) {
+ mAm = service;
+ mContext = service.mContext;
+ }
+
+ public boolean isEnabled() {
+ synchronized (mLock) {
+ return mEnabled;
+ }
+ }
+
+ /**
+ * When there's any change to packages, we refresh all the aliases.
+ * TODO: In the production version, we should update only the changed package.
+ */
+ final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageModified(String packageName) {
+ refresh();
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ refresh();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ refresh();
+ }
+ };
+
+ /**
+ * (Re-)loads aliases from <meta-data> and the device config override.
+ */
+ public void update(boolean enabled, String overrides) {
+ synchronized (mLock) {
+ if (enabled == mEnabled && Objects.equals(overrides, mOverrideString)) {
+ return;
+ }
+ if (enabled != mEnabled) {
+ Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases...");
+ if (enabled) {
+ mPackageMonitor.register(mAm.mContext, UserHandle.ALL,
+ /* externalStorage= */ false, BackgroundThread.getHandler());
+ } else {
+ mPackageMonitor.unregister();
+ }
+ }
+ mEnabled = enabled;
+ mOverrideString = overrides;
+
+ if (mEnabled) {
+ refreshLocked();
+ } else {
+ mFromTo.clear();
+ }
+ }
+ }
+
+ private void refresh() {
+ synchronized (mLock) {
+ refreshLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void refreshLocked() {
+ if (DEBUG) Slog.d(TAG, "Refreshing aliases...");
+ mFromTo.clear();
+ loadFromMetadataLocked();
+ loadOverridesLocked();
+ }
+
+ /**
+ * Scans all the "alias" components and inserts the from-to pairs to the map.
+ */
+ @GuardedBy("mLock")
+ private void loadFromMetadataLocked() {
+ if (DEBUG) Slog.d(TAG, "Scanning aliases...");
+ Intent i = new Intent(ALIAS_FILTER_ACTION);
+
+ List<ResolveInfo> services = mContext.getPackageManager().queryIntentServicesAsUser(
+ i,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_ANY_USER
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_META_DATA,
+ UserHandle.USER_SYSTEM);
+
+ for (ResolveInfo ri : services) {
+ final ComponentInfo ci = ri.getComponentInfo();
+ final ComponentName from = ci.getComponentName();
+ final ComponentName to = ComponentName.unflattenFromString(
+ ci.metaData.getString(META_DATA_ALIAS_TARGET));
+ if (!validateComponentName(to)) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "" + from.flattenToShortString() + " -> " + to.flattenToShortString());
+ }
+ mFromTo.put(from, to);
+ }
+
+ // TODO: Scan for other component types as well.
+ }
+
+ /**
+ * Parses an "override" string and inserts the from-to pairs to the map.
+ *
+ * The format is:
+ * ALIAS-COMPONENT-1 ":" TARGET-COMPONENT-1 ( "," ALIAS-COMPONENT-2 ":" TARGET-COMPONENT-2 )*
+ */
+ @GuardedBy("mLock")
+ private void loadOverridesLocked() {
+ if (DEBUG) Slog.d(TAG, "Loading aliases overrides ...");
+ for (String line : mOverrideString.split("\\+")) {
+ final String[] fields = line.split("\\:+", 2);
+ final ComponentName from = ComponentName.unflattenFromString(fields[0]);
+ if (!validateComponentName(from)) {
+ continue;
+ }
+
+ if (fields.length == 1) {
+ if (DEBUG) Slog.d(TAG, "" + from.flattenToShortString() + " [removed]");
+ mFromTo.remove(from);
+ } else {
+ final ComponentName to = ComponentName.unflattenFromString(fields[1]);
+ if (!validateComponentName(to)) {
+ continue;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "" + from.flattenToShortString() + " -> " + to.flattenToShortString());
+ }
+ mFromTo.put(from, to);
+ }
+ }
+ }
+
+ private boolean validateComponentName(ComponentName cn) {
+ if (cn != null) {
+ return true;
+ }
+ Slog.e(TAG, "Invalid component name detected: " + cn);
+ return false;
+ }
+
+ /**
+ * Dump the aliases for dumpsys / bugrports.
+ */
+ public void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("ACTIVITY MANAGER COMPONENT-ALIAS (dumpsys activity component-alias)");
+ pw.print(" Enabled: "); pw.println(mEnabled);
+
+ pw.println(" Aliases:");
+ for (int i = 0; i < mFromTo.size(); i++) {
+ ComponentName from = mFromTo.keyAt(i);
+ ComponentName to = mFromTo.valueAt(i);
+ pw.print(" ");
+ pw.print(from.flattenToShortString());
+ pw.print(" -> ");
+ pw.print(to.flattenToShortString());
+ pw.println();
+ }
+ pw.println();
+ }
+ }
+
+ /**
+ * Contains alias resolution information.
+ */
+ public static class Resolution {
+ @NonNull
+ public final Intent sourceIntent;
+
+ /** "From" component. Null if component alias is disabled. */
+ @Nullable
+ public final ComponentName sourceComponent;
+
+ /** "To" component. Null if component alias is disabled, or the source isn't an alias. */
+ @Nullable
+ public final ComponentName resolvedComponent;
+
+ public Resolution(Intent sourceIntent,
+ ComponentName sourceComponent, ComponentName resolvedComponent) {
+ this.sourceIntent = sourceIntent;
+ this.sourceComponent = sourceComponent;
+ this.resolvedComponent = resolvedComponent;
+ }
+
+ @Nullable
+ public boolean isAlias() {
+ return this.resolvedComponent != null;
+ }
+
+ @Nullable
+ public ComponentName getAliasComponent() {
+ return isAlias() ? sourceComponent : null;
+ }
+
+ @Nullable
+ public ComponentName getTargetComponent() {
+ return isAlias() ? resolvedComponent : null;
+ }
+ }
+
+ @Nullable
+ public Resolution resolveService(
+ @NonNull Intent service, @Nullable String resolvedType,
+ int packageFlags, int userId, int callingUid) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return new Resolution(service, null, null);
+ }
+
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+
+ ResolveInfo rInfo = pmi.resolveService(service,
+ resolvedType, packageFlags, userId, callingUid);
+ ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null;
+ if (sInfo == null) {
+ return null; // Service not found.
+ }
+ final ComponentName alias =
+ new ComponentName(sInfo.applicationInfo.packageName, sInfo.name);
+ final ComponentName target = mFromTo.get(alias);
+
+ if (target != null) {
+ // It's an alias. Keep the original intent, and rewrite it.
+ service.setOriginalIntent(new Intent(service));
+
+ service.setPackage(null);
+ service.setComponent(target);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Alias resolved: " + alias.flattenToShortString()
+ + " -> " + target.flattenToShortString());
+ }
+ }
+ return new Resolution(service, alias, target);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 9161271..b434328 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -18,8 +18,10 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import android.annotation.Nullable;
import android.app.IServiceConnection;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -48,6 +50,12 @@
String stringName; // Caching of toString.
boolean serviceDead; // Well is it?
private Object mProcStatsLock; // Internal lock for accessing AssociationState
+ /**
+ * If the connection was made against an alias, then the alias conponent name. Otherwise, null.
+ * We return this component name to the client.
+ */
+ @Nullable
+ final ComponentName aliasComponent;
// Please keep the following two enum list synced.
private static final int[] BIND_ORIG_ENUMS = new int[] {
@@ -102,7 +110,8 @@
ActivityServiceConnectionsHolder<ConnectionRecord> _activity,
IServiceConnection _conn, int _flags,
int _clientLabel, PendingIntent _clientIntent,
- int _clientUid, String _clientProcessName, String _clientPackageName) {
+ int _clientUid, String _clientProcessName, String _clientPackageName,
+ ComponentName _aliasComponent) {
binding = _binding;
activity = _activity;
conn = _conn;
@@ -112,6 +121,7 @@
clientUid = _clientUid;
clientProcessName = _clientProcessName;
clientPackageName = _clientPackageName;
+ aliasComponent = _aliasComponent;
}
public boolean hasFlag(final int flag) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 18f1267..284a754 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2174,7 +2174,7 @@
ConnectionRecord cr = spy(new ConnectionRecord(binding,
mock(ActivityServiceConnectionsHolder.class),
mock(IServiceConnection.class), bindFlags,
- 0, null, client.uid, client.processName, client.info.packageName));
+ 0, null, client.uid, client.processName, client.info.packageName, null));
doCallRealMethod().when(record).addConnection(any(IBinder.class),
any(ConnectionRecord.class));
record.addConnection(binder, cr);
diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp
new file mode 100644
index 0000000..15a680d
--- /dev/null
+++ b/tests/componentalias/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "ComponentAliasTestsCommon",
+ srcs: [
+ "common/**/*.java",
+ ],
+ plugins: [
+ "staledataclass-annotation-processor",
+ ],
+ platform_apis: true, // We use hidden APIs in the test.
+}
+
+android_test {
+ name: "ComponentAliasTests",
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "mockito-target-extended-minus-junit4",
+ "truth-prebuilt",
+ "ub-uiautomator",
+ "ComponentAliasTestsCommon",
+ ],
+ libs: ["android.test.base"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ platform_apis: true, // We use hidden APIs in the test.
+}
diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml
new file mode 100755
index 0000000..893be8e
--- /dev/null
+++ b/tests/componentalias/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.componentalias.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.componentalias.tests" >
+ </instrumentation>
+</manifest>
diff --git a/tests/componentalias/AndroidTest.xml b/tests/componentalias/AndroidTest.xml
new file mode 100644
index 0000000..e2c37d2
--- /dev/null
+++ b/tests/componentalias/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="ComponentAliasTests.apk" />
+ <option name="test-file-name" value="ComponentAliasAppMain.apk" />
+ <option name="test-file-name" value="ComponentAliasAppSub1.apk" />
+ <option name="test-file-name" value="ComponentAliasAppSub2.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. -->
+ <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.app" />
+ <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.app.sub1" />
+ <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.app.sub2" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.content.componentalias.tests" />
+ <option name="runtime-hint" value="2m" />
+ <option name="isolated-storage" value="false" />
+ </test>
+</configuration>
diff --git a/tests/componentalias/OWNERS b/tests/componentalias/OWNERS
new file mode 100644
index 0000000..1073c81
--- /dev/null
+++ b/tests/componentalias/OWNERS
@@ -0,0 +1,2 @@
+omakoto@google.com
+yamasani@google.com
\ No newline at end of file
diff --git a/tests/componentalias/apps/Android.bp b/tests/componentalias/apps/Android.bp
new file mode 100644
index 0000000..b2cb0f7
--- /dev/null
+++ b/tests/componentalias/apps/Android.bp
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// Copyright (C) 2018 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// We build three helper APKs from the same source.
+// - The main APK (ComponentAliasAppMain), which contains both the alias and the target components.
+// - The sub APKs (ComponentAliasAppSub*), which only contains the target components.
+
+java_defaults {
+ name: "component_alias_app_defaults",
+ libs: ["android.test.base"],
+ sdk_version: "test_current",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.legacy_legacy-support-v4",
+ "androidx.test.rules",
+ "ComponentAliasTestsCommon",
+ ],
+}
+
+android_test_helper_app {
+ name: "ComponentAliasAppMain",
+ defaults: [
+ "component_alias_app_defaults",
+ ],
+ package_name: "android.content.componentalias.tests.app",
+ manifest: "AndroidManifest_main.xml",
+}
+
+android_test_helper_app {
+ name: "ComponentAliasAppSub1",
+ defaults: [
+ "component_alias_app_defaults",
+ ],
+ package_name: "android.content.componentalias.tests.app.sub1",
+ manifest: "AndroidManifest_sub.xml",
+}
+
+android_test_helper_app {
+ name: "ComponentAliasAppSub2",
+ defaults: [
+ "component_alias_app_defaults",
+ ],
+ package_name: "android.content.componentalias.tests.app.sub2",
+ manifest: "AndroidManifest_sub.xml",
+}
diff --git a/tests/componentalias/apps/AndroidManifest_main.xml b/tests/componentalias/apps/AndroidManifest_main.xml
new file mode 100755
index 0000000..2caef3e
--- /dev/null
+++ b/tests/componentalias/apps/AndroidManifest_main.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.componentalias.tests.app" >
+
+ <application>
+ <!--
+ Alias components.
+ Note aliases do not have the actual implementation, because they're never called
+ directly.
+ -->
+
+ <service android:name=".s.Alias01" android:exported="true" android:enabled="true" >
+ <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.app.sub1/android.content.componentalias.tests.app.s.Target01" />
+ <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
+ <intent-filter><action android:name="android.content.componentalias.tests.app.IS_ALIAS_01" /></intent-filter>
+ </service>
+ <service android:name=".s.Alias02" android:exported="true" android:enabled="true" >
+ <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.app.sub2/android.content.componentalias.tests.app.s.Target02" />
+ <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
+ <intent-filter><action android:name="android.content.componentalias.tests.app.IS_ALIAS_02" /></intent-filter>
+ </service>
+ <service android:name=".s.Alias03" android:exported="true" android:enabled="true" >
+ <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.app.sub1/android.content.componentalias.tests.app.s.Target03" />
+ <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
+ <intent-filter><action android:name="android.content.componentalias.tests.app.IS_ALIAS_03" /></intent-filter>
+ </service>
+ <service android:name=".s.Alias04" android:exported="true" android:enabled="true" >
+ <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.app.sub2/android.content.componentalias.tests.app.s.Target04" />
+ <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
+ <intent-filter><action android:name="android.content.componentalias.tests.app.IS_ALIAS_04" /></intent-filter>
+ </service>
+ <service android:name=".s.Alias05" android:exported="true" android:enabled="true" >
+ <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.app.sub1/android.content.componentalias.tests.app.s.Target05" />
+ <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
+ <intent-filter><action android:name="android.content.componentalias.tests.app.IS_ALIAS_05" /></intent-filter>
+ </service>
+
+ <!-- Target components -->
+
+ <service android:name=".s.Target01" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target02" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target03" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target04" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target05" android:exported="true" android:enabled="true" >
+ </service>
+ </application>
+</manifest>
diff --git a/tests/componentalias/apps/AndroidManifest_sub.xml b/tests/componentalias/apps/AndroidManifest_sub.xml
new file mode 100755
index 0000000..2ddd965
--- /dev/null
+++ b/tests/componentalias/apps/AndroidManifest_sub.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.componentalias.tests.app" >
+
+ <application>
+ <!-- Only contain the target components -->
+
+ <service android:name=".s.Target01" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target02" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target03" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target04" android:exported="true" android:enabled="true" >
+ </service>
+ <service android:name=".s.Target05" android:exported="true" android:enabled="true" >
+ </service>
+ </application>
+</manifest>
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/BaseService.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/BaseService.java
new file mode 100644
index 0000000..fb67829
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/BaseService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.TAG;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.TEST_PACKAGE;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.componentalias.tests.common.ComponentAliasMessage;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+
+public class BaseService extends Service {
+ private String getMyIdentity() {
+ return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName()))
+ .flattenToShortString();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent);
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity(getMyIdentity())
+ .setMethodName("onStartCommand")
+ .setIntent(intent);
+ BroadcastMessenger.send(this, TEST_PACKAGE, m);
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy: on " + getMyIdentity());
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity(getMyIdentity())
+ .setMethodName("onDestroy");
+ BroadcastMessenger.send(this, TEST_PACKAGE, m);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent);
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity(getMyIdentity())
+ .setMethodName("onBind")
+ .setIntent(intent);
+ BroadcastMessenger.send(this, TEST_PACKAGE, m);
+
+ return new Binder();
+ }
+}
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target01.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target01.java
new file mode 100644
index 0000000..87e48cb
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target01.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+public class Target01 extends BaseService {
+}
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target02.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target02.java
new file mode 100644
index 0000000..0e8a6a8
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target02.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+public class Target02 extends BaseService {
+}
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target03.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target03.java
new file mode 100644
index 0000000..b7990bb
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target03.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+public class Target03 extends BaseService {
+}
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target04.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target04.java
new file mode 100644
index 0000000..0e51a6b
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target04.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+public class Target04 extends BaseService {
+}
diff --git a/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target05.java b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target05.java
new file mode 100644
index 0000000..77060cd
--- /dev/null
+++ b/tests/componentalias/apps/src/android/content/componentalias/tests/app/s/Target05.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.app.s;
+
+public class Target05 extends BaseService {
+}
diff --git a/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java b/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java
new file mode 100644
index 0000000..399c070
--- /dev/null
+++ b/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger.
+ *
+ * To add a new field, just add a private member field, and run:
+ * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java
+ */
+@DataClass(
+ genConstructor = false,
+ genSetters = true,
+ genToString = true,
+ genAidl = false)
+public final class ComponentAliasMessage implements Parcelable {
+ public ComponentAliasMessage() {
+ }
+
+ @Nullable
+ private String mMessage;
+
+ @Nullable
+ private String mMethodName;
+
+ @Nullable
+ private String mSenderIdentity;
+
+ @Nullable
+ private Intent mIntent;
+
+ @Nullable
+ private ComponentName mComponent;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public @Nullable String getMessage() {
+ return mMessage;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getMethodName() {
+ return mMethodName;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getSenderIdentity() {
+ return mSenderIdentity;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Intent getIntent() {
+ return mIntent;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable ComponentName getComponent() {
+ return mComponent;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ComponentAliasMessage setMessage(@NonNull String value) {
+ mMessage = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) {
+ mMethodName = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) {
+ mSenderIdentity = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) {
+ mIntent = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) {
+ mComponent = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ComponentAliasMessage { " +
+ "message = " + mMessage + ", " +
+ "methodName = " + mMethodName + ", " +
+ "senderIdentity = " + mSenderIdentity + ", " +
+ "intent = " + mIntent + ", " +
+ "component = " + mComponent +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mMessage != null) flg |= 0x1;
+ if (mMethodName != null) flg |= 0x2;
+ if (mSenderIdentity != null) flg |= 0x4;
+ if (mIntent != null) flg |= 0x8;
+ if (mComponent != null) flg |= 0x10;
+ dest.writeByte(flg);
+ if (mMessage != null) dest.writeString(mMessage);
+ if (mMethodName != null) dest.writeString(mMethodName);
+ if (mSenderIdentity != null) dest.writeString(mSenderIdentity);
+ if (mIntent != null) dest.writeTypedObject(mIntent, flags);
+ if (mComponent != null) dest.writeTypedObject(mComponent, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ComponentAliasMessage(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String message = (flg & 0x1) == 0 ? null : in.readString();
+ String methodName = (flg & 0x2) == 0 ? null : in.readString();
+ String senderIdentity = (flg & 0x4) == 0 ? null : in.readString();
+ Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR);
+ ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
+
+ this.mMessage = message;
+ this.mMethodName = methodName;
+ this.mSenderIdentity = senderIdentity;
+ this.mIntent = intent;
+ this.mComponent = component;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR
+ = new Parcelable.Creator<ComponentAliasMessage>() {
+ @Override
+ public ComponentAliasMessage[] newArray(int size) {
+ return new ComponentAliasMessage[size];
+ }
+
+ @Override
+ public ComponentAliasMessage createFromParcel(@NonNull Parcel in) {
+ return new ComponentAliasMessage(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1629137098129L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasMessage.java",
+ inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasTestCommon.java b/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasTestCommon.java
new file mode 100644
index 0000000..f306072
--- /dev/null
+++ b/tests/componentalias/common/android/content/componentalias/tests/common/ComponentAliasTestCommon.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests.common;
+
+public final class ComponentAliasTestCommon {
+ private ComponentAliasTestCommon() {
+ }
+
+ public static final String TAG = "ComponentAliasTest";
+
+ public static final String TEST_PACKAGE = "android.content.componentalias.tests";
+
+ public static final String APP_PACKAGE = "android.content.componentalias.tests.app";
+ public static final String SUB1_PACKAGE = "android.content.componentalias.tests.app.sub1";
+ public static final String SUB2_PACKAGE = "android.content.componentalias.tests.app.sub2";
+}
diff --git a/tests/componentalias/common/com/android/compatibility/common/util/BroadcastMessenger.java b/tests/componentalias/common/com/android/compatibility/common/util/BroadcastMessenger.java
new file mode 100644
index 0000000..b6dc09f
--- /dev/null
+++ b/tests/componentalias/common/com/android/compatibility/common/util/BroadcastMessenger.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * Provides a one-way communication mechanism using a Parcelable as a payload, via broadcasts.
+ *
+ * TODO: Move it to compatibility-device-util-axt.
+ */
+public final class BroadcastMessenger {
+ private static final String TAG = "BroadcastMessenger";
+
+ private static final String ACTION_MESSAGE =
+ "com.android.compatibility.common.util.BroadcastMessenger.ACTION_MESSAGE";
+ private static final String EXTRA_MESSAGE =
+ "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_MESSAGE";
+
+ /** Send a message to the {@link Receiver} in a given package. */
+ public static <T extends Parcelable> void send(@NonNull Context context,
+ @NonNull String receiverPackage,
+ @NonNull T message) {
+ final Intent i = new Intent(ACTION_MESSAGE);
+ i.putExtra(EXTRA_MESSAGE, message);
+ i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ i.setPackage(receiverPackage);
+
+ Log.d(TAG, "Sending: " + message);
+ context.sendBroadcast(i);
+ }
+
+ /**
+ * Receive messages from the test app.
+ */
+ public static final class Receiver<T extends Parcelable> implements AutoCloseable {
+ private final Context mContext;
+ @GuardedBy("mMessages")
+ private final ArrayList<T> mMessages = new ArrayList<>();
+ private boolean mRegistered;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!ACTION_MESSAGE.equals(intent.getAction())) {
+ throw new RuntimeException("Unknown message received: " + intent);
+ }
+ final T message = intent.getParcelableExtra(EXTRA_MESSAGE);
+ Log.d(TAG, "Received: " + message);
+ synchronized (mMessages) {
+ mMessages.add(message);
+ mMessages.notifyAll();
+ }
+ }
+ };
+
+ /**
+ * Constructor.
+ */
+ public Receiver(@NonNull Context context) {
+ mContext = context;
+
+ final IntentFilter fi = new IntentFilter(ACTION_MESSAGE);
+ context.registerReceiver(mReceiver, fi);
+ mRegistered = true;
+ }
+
+ @Override
+ public void close() {
+ if (mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mRegistered = false;
+ }
+ }
+
+ /**
+ * Receive the next message with a 60 second timeout.
+ */
+ @NonNull
+ public T waitForNextMessage() throws Exception {
+ return waitForNextMessage(60_000);
+ }
+
+ /**
+ * Receive the next message.
+ */
+ @NonNull
+ public T waitForNextMessage(long timeoutMillis) throws Exception {
+ synchronized (mMessages) {
+ final long timeout = System.currentTimeMillis() + timeoutMillis;
+ while (mMessages.size() == 0) {
+ final long wait = timeout - System.currentTimeMillis();
+ if (wait <= 0) {
+ throw new RuntimeException("Timeout waiting for the next message");
+ }
+ mMessages.wait(wait);
+ }
+ return mMessages.remove(0);
+ }
+ }
+ }
+}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
new file mode 100644
index 0000000..1de6277
--- /dev/null
+++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.componentalias.tests;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.APP_PACKAGE;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.SUB1_PACKAGE;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.SUB2_PACKAGE;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.TAG;
+import static android.content.componentalias.tests.common.ComponentAliasTestCommon.TEST_PACKAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.componentalias.tests.common.ComponentAliasMessage;
+import android.os.IBinder;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.ShellUtils;
+import com.android.compatibility.common.util.TestUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Test for the experimental "Component alias" feature.
+ *
+ * Note this test exercises the relevant APIs, but don't actually check if the aliases are
+ * resolved.
+ *
+ * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run
+ * BG services.
+ */
+public class ComponentAliasServiceTest {
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+ private static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
+ @Before
+ public void enableComponentAlias() throws Exception {
+ sDeviceConfig.set("component_alias_overrides", "");
+ sDeviceConfig.set("enable_experimental_component_alias", "true");
+
+ // Device config propagation happens on a handler, so we need to wait for AM to
+ // actually set it.
+ TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
+ return ShellUtils.runShellCommand("dumpsys activity component-alias")
+ .indexOf("Enabled: true") > 0;
+ });
+ }
+
+ @AfterClass
+ public static void restoreDeviceConfig() throws Exception {
+ sDeviceConfig.close();
+ }
+
+ /**
+ * Service connection used throughout the tests. It sends a message for each callback via
+ * the messenger.
+ */
+ private static final ServiceConnection sServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.w(TAG, "onServiceConnected: " + name);
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity("sServiceConnection")
+ .setMethodName("onServiceConnected")
+ .setComponent(name);
+
+ BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.w(TAG, "onServiceDisconnected: " + name);
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity("sServiceConnection")
+ .setMethodName("onServiceDisconnected")
+ .setComponent(name);
+
+ BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.w(TAG, "onBindingDied: " + name);
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity("sServiceConnection")
+ .setMethodName("onBindingDied");
+
+ BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.w(TAG, "onNullBinding: " + name);
+
+ ComponentAliasMessage m = new ComponentAliasMessage()
+ .setSenderIdentity("sServiceConnection")
+ .setMethodName("onNullBinding");
+
+ BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
+ }
+ };
+
+ private void testStartAndStopService_common(
+ Intent originalIntent,
+ ComponentName componentNameForClient,
+ ComponentName componentNameForTarget) throws Exception {
+
+ ComponentAliasMessage m;
+
+ try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext)) {
+ // Start the service.
+ ComponentName result = sContext.startService(originalIntent);
+ assertThat(result).isEqualTo(componentNameForClient);
+
+ // Check
+ m = receiver.waitForNextMessage();
+
+ assertThat(m.getMethodName()).isEqualTo("onStartCommand");
+ // The app sees the rewritten intent.
+ assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
+
+ // Verify the original intent.
+ assertThat(m.getIntent().getOriginalIntent().getComponent())
+ .isEqualTo(originalIntent.getComponent());
+ assertThat(m.getIntent().getOriginalIntent().getPackage())
+ .isEqualTo(originalIntent.getPackage());
+
+ // Stop the service.
+ sContext.stopService(originalIntent);
+
+ // Check
+ m = receiver.waitForNextMessage();
+
+ assertThat(m.getMethodName()).isEqualTo("onDestroy");
+ }
+ }
+
+ @Test
+ public void testStartAndStopService_explicitComponentName() throws Exception {
+ Intent i = new Intent().setComponent(
+ new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias01"));
+
+ ComponentName alias = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias01");
+ ComponentName target = new ComponentName(SUB1_PACKAGE, APP_PACKAGE + ".s.Target01");
+
+ testStartAndStopService_common(i, alias, target);
+ }
+
+ @Test
+ public void testStartAndStopService_explicitPackageName() throws Exception {
+ Intent i = new Intent().setPackage(APP_PACKAGE);
+ i.setAction(APP_PACKAGE + ".IS_ALIAS_02");
+
+ ComponentName alias = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02");
+ ComponentName target = new ComponentName(SUB2_PACKAGE, APP_PACKAGE + ".s.Target02");
+
+ testStartAndStopService_common(i, alias, target);
+ }
+
+ @Test
+ public void testStartAndStopService_override() throws Exception {
+ Intent i = new Intent().setPackage(APP_PACKAGE);
+ i.setAction(APP_PACKAGE + ".IS_ALIAS_02");
+
+ ComponentName alias = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02");
+
+ // Note, alias02 originally points at sub*2* package, but we override it in this test.
+ ComponentName target = new ComponentName(SUB1_PACKAGE, APP_PACKAGE + ".s.Target02");
+
+ sDeviceConfig.set("component_alias_overrides",
+ alias.flattenToShortString() + ":" + target.flattenToShortString());
+
+ TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
+ return ShellUtils.runShellCommand("dumpsys activity component-alias")
+ .indexOf(alias.flattenToShortString() + " -> " + target.flattenToShortString())
+ > 0;
+ });
+
+
+ testStartAndStopService_common(i, alias, target);
+ }
+
+ private void testBindAndUnbindService_common(
+ Intent originalIntent,
+ ComponentName componentNameForClient,
+ ComponentName componentNameForTarget) throws Exception {
+ ComponentAliasMessage m;
+
+ try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext)) {
+ // Bind to the service.
+ assertThat(sContext.bindService(
+ originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue();
+
+ // Check the target side behavior.
+ m = receiver.waitForNextMessage();
+
+ assertThat(m.getMethodName()).isEqualTo("onBind");
+ // The app sees the rewritten intent.
+ assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
+
+ // Verify the original intent.
+ assertThat(m.getIntent().getOriginalIntent().getComponent())
+ .isEqualTo(originalIntent.getComponent());
+ assertThat(m.getIntent().getOriginalIntent().getPackage())
+ .isEqualTo(originalIntent.getPackage());
+
+ // Check the client side behavior.
+ m = receiver.waitForNextMessage();
+
+ assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
+ // The app sees the rewritten intent.
+ assertThat(m.getComponent()).isEqualTo(componentNameForClient);
+
+ // Unbind.
+ sContext.unbindService(sServiceConnection);
+
+ // Check the target side behavior.
+ m = receiver.waitForNextMessage();
+
+ assertThat(m.getMethodName()).isEqualTo("onDestroy");
+
+ // Note onServiceDisconnected() won't be called in this case.
+ }
+ }
+
+ @Test
+ public void testBindService_explicitComponentName() throws Exception {
+ Intent i = new Intent().setComponent(
+ new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias01"));
+
+ testBindAndUnbindService_common(i,
+ new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias01"),
+ new ComponentName(SUB1_PACKAGE, APP_PACKAGE + ".s.Target01"));
+ }
+
+ @Test
+ public void testBindService_explicitPackageName() throws Exception {
+ Intent i = new Intent().setPackage(APP_PACKAGE);
+ i.setAction(APP_PACKAGE + ".IS_ALIAS_02");
+
+ testBindAndUnbindService_common(i,
+ new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02"),
+ new ComponentName(SUB2_PACKAGE, APP_PACKAGE + ".s.Target02"));
+ }
+}