Bug fix + clean up on component alias

- The "overrides" device config was supposed to use "," as a separator,
  but accidentally using "+" instead.

- Improved BroadcastMessenger.

- ComponentAliasResolver.Resolution now is a generic class.
  I'm using it with String for provider authority aliases.

Bug: 197264681
Test: atest ComponentAliasServiceTest
Change-Id: Iaa8f4e67bfd8cae93755f68d821da0ba9f48dd41
This commit is contained in:
Makoto Onuki
2021-08-20 14:01:21 -07:00
parent b0ede0654f
commit b7e9674df7
4 changed files with 161 additions and 51 deletions

View File

@@ -3211,7 +3211,7 @@ public final class ActiveServices {
// 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 =
final ComponentAliasResolver.Resolution<ComponentName> resolution =
mAm.mComponentAliasResolver.resolveService(service, resolvedType,
/* match flags */ 0, userId, callingUid);
@@ -3448,7 +3448,7 @@ public final class ActiveServices {
return null;
}
}
return new ServiceLookupResult(r, resolution.getAliasComponent());
return new ServiceLookupResult(r, resolution.getAlias());
}
return null;
}
@@ -4448,6 +4448,8 @@ public final class ActiveServices {
// being brought down. Mark it as dead.
cr.serviceDead = true;
cr.stopAssociation();
final ComponentName clientSideComponentName =
cr.aliasComponent != null ? cr.aliasComponent : r.name;
try {
cr.conn.connected(r.name, null, true);
} catch (Exception e) {

View File

@@ -189,7 +189,7 @@ public class ComponentAliasResolver {
@GuardedBy("mLock")
private void loadOverridesLocked() {
if (DEBUG) Slog.d(TAG, "Loading aliases overrides ...");
for (String line : mOverrideString.split("\\+")) {
for (String line : mOverrideString.split("\\,+")) {
final String[] fields = line.split("\\:+", 2);
final ComponentName from = ComponentName.unflattenFromString(fields[0]);
if (!validateComponentName(from)) {
@@ -247,50 +247,45 @@ public class ComponentAliasResolver {
/**
* Contains alias resolution information.
*/
public static class Resolution {
@NonNull
public final Intent sourceIntent;
public static class Resolution<T> {
/** "From" component. Null if component alias is disabled. */
@Nullable
public final ComponentName sourceComponent;
public final T source;
/** "To" component. Null if component alias is disabled, or the source isn't an alias. */
@Nullable
public final ComponentName resolvedComponent;
public final T resolved;
public Resolution(Intent sourceIntent,
ComponentName sourceComponent, ComponentName resolvedComponent) {
this.sourceIntent = sourceIntent;
this.sourceComponent = sourceComponent;
this.resolvedComponent = resolvedComponent;
public Resolution(T source, T resolved) {
this.source = source;
this.resolved = resolved;
}
@Nullable
public boolean isAlias() {
return this.resolvedComponent != null;
return this.resolved != null;
}
@Nullable
public ComponentName getAliasComponent() {
return isAlias() ? sourceComponent : null;
public T getAlias() {
return isAlias() ? source : null;
}
@Nullable
public ComponentName getTargetComponent() {
return isAlias() ? resolvedComponent : null;
public T getTarget() {
return isAlias() ? resolved : null;
}
}
@Nullable
public Resolution resolveService(
public Resolution<ComponentName> 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);
return new Resolution<>(null, null);
}
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
@@ -317,7 +312,7 @@ public class ComponentAliasResolver {
+ " -> " + target.flattenToShortString());
}
}
return new Resolution(service, alias, target);
return new Resolution<>(alias, target);
}
} finally {
Binder.restoreCallingIdentity(identity);

View File

@@ -20,10 +20,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -37,39 +40,83 @@ public final class BroadcastMessenger {
private static final String ACTION_MESSAGE =
"com.android.compatibility.common.util.BroadcastMessenger.ACTION_MESSAGE";
private static final String ACTION_PING =
"com.android.compatibility.common.util.BroadcastMessenger.ACTION_PING";
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);
/**
* We need to drop messages that were sent before the receiver was created. We keep
* track of the message send time in this extra.
*/
private static final String EXTRA_SENT_TIME =
"com.android.compatibility.common.util.BroadcastMessenger.EXTRA_SENT_TIME";
private static long getCurrentTime() {
return SystemClock.uptimeMillis();
}
private static void sendBroadcast(@NonNull Intent i,
@NonNull Context context, @NonNull String receiverPackage) {
i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
i.setPackage(receiverPackage);
i.putExtra(EXTRA_SENT_TIME, getCurrentTime());
Log.d(TAG, "Sending: " + message);
context.sendBroadcast(i);
}
/** 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, Preconditions.checkNotNull(message));
Log.i(TAG, "Sending: " + message);
sendBroadcast(i, context, receiverPackage);
}
private static void sendPing(@NonNull Context context, @NonNull String receiverPackage) {
final Intent i = new Intent(ACTION_PING);
Log.i(TAG, "Sending a ping");
sendBroadcast(i, context, receiverPackage);
}
/**
* Receive messages from the test app.
* Receive messages sent with {@link #send}. Note it'll ignore all the messages that were
* sent before instantiated.
*/
public static final class Receiver<T extends Parcelable> implements AutoCloseable {
private final Context mContext;
private final HandlerThread mReceiverThread = new HandlerThread(TAG);
@GuardedBy("mMessages")
private final ArrayList<T> mMessages = new ArrayList<>();
private final long mCreatedTime = getCurrentTime();
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);
// Log.d(TAG, "Received intent: " + intent);
switch (intent.getAction()) {
case ACTION_MESSAGE:
case ACTION_PING:
break;
default:
throw new RuntimeException("Unknown broadcast received: " + intent);
}
if (intent.getLongExtra(EXTRA_SENT_TIME, 0) < mCreatedTime) {
Log.i(TAG, "Dropping stale broadcast: " + intent);
return;
}
// Note for a PING, the message will be null.
final T message = intent.getParcelableExtra(EXTRA_MESSAGE);
Log.d(TAG, "Received: " + message);
if (message != null) {
Log.i(TAG, "Received: " + message);
}
synchronized (mMessages) {
mMessages.add(message);
mMessages.notifyAll();
@@ -83,8 +130,13 @@ public final class BroadcastMessenger {
public Receiver(@NonNull Context context) {
mContext = context;
mReceiverThread.start();
final IntentFilter fi = new IntentFilter(ACTION_MESSAGE);
context.registerReceiver(mReceiver, fi);
fi.addAction(ACTION_PING);
context.registerReceiver(mReceiver, fi, /** permission=*/ null,
mReceiverThread.getThreadHandler());
mRegistered = true;
}
@@ -92,6 +144,7 @@ public final class BroadcastMessenger {
public void close() {
if (mRegistered) {
mContext.unregisterReceiver(mReceiver);
mReceiverThread.quit();
mRegistered = false;
}
}
@@ -121,5 +174,21 @@ public final class BroadcastMessenger {
return mMessages.remove(0);
}
}
/**
* Ensure that no further messages have been received.
*
* Call it before {@link #close()}.
*/
public void ensureNoMoreMessages() throws Exception {
// Send a ping to myself.
sendPing(mContext, mContext.getPackageName());
final T m = waitForNextMessage();
if (m == null) {
return; // Okay. Ping will deliver a null message.
}
throw new RuntimeException("No more messages expected, but received: " + m);
}
}
}

View File

@@ -21,7 +21,6 @@ import static android.content.componentalias.tests.common.ComponentAliasTestComm
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;
@@ -34,18 +33,16 @@ import android.os.IBinder;
import android.provider.DeviceConfig;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
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;
/**
@@ -95,7 +92,7 @@ public class ComponentAliasServiceTest {
.setMethodName("onServiceConnected")
.setComponent(name);
BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
BroadcastMessenger.send(sContext, sContext.getPackageName(), m);
}
@Override
@@ -107,7 +104,7 @@ public class ComponentAliasServiceTest {
.setMethodName("onServiceDisconnected")
.setComponent(name);
BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
BroadcastMessenger.send(sContext, sContext.getPackageName(), m);
}
@Override
@@ -118,7 +115,7 @@ public class ComponentAliasServiceTest {
.setSenderIdentity("sServiceConnection")
.setMethodName("onBindingDied");
BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
BroadcastMessenger.send(sContext, sContext.getPackageName(), m);
}
@Override
@@ -129,7 +126,7 @@ public class ComponentAliasServiceTest {
.setSenderIdentity("sServiceConnection")
.setMethodName("onNullBinding");
BroadcastMessenger.send(sContext, TEST_PACKAGE, m);
BroadcastMessenger.send(sContext, sContext.getPackageName(), m);
}
};
@@ -165,6 +162,8 @@ public class ComponentAliasServiceTest {
m = receiver.waitForNextMessage();
assertThat(m.getMethodName()).isEqualTo("onDestroy");
receiver.ensureNoMoreMessages();
}
}
@@ -193,24 +192,29 @@ public class ComponentAliasServiceTest {
@Test
public void testStartAndStopService_override() throws Exception {
Intent i = new Intent().setPackage(APP_PACKAGE);
i.setAction(APP_PACKAGE + ".IS_ALIAS_02");
i.setAction(APP_PACKAGE + ".IS_ALIAS_01");
ComponentName alias = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02");
// Change some of the aliases from what's defined in <meta-data>.
// 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");
ComponentName aliasA = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias01");
ComponentName targetA = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Target02");
ComponentName aliasB = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02");
ComponentName targetB = new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Target01");
sDeviceConfig.set("component_alias_overrides",
alias.flattenToShortString() + ":" + target.flattenToShortString());
aliasA.flattenToShortString() + ":" + targetA.flattenToShortString()
+ ","
+ aliasB.flattenToShortString() + ":" + targetB.flattenToShortString());
TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
return ShellUtils.runShellCommand("dumpsys activity component-alias")
.indexOf(alias.flattenToShortString() + " -> " + target.flattenToShortString())
> 0;
.indexOf(aliasA.flattenToShortString()
+ " -> " + targetA.flattenToShortString()) > 0;
});
testStartAndStopService_common(i, alias, target);
testStartAndStopService_common(i, aliasA, targetA);
}
private void testBindAndUnbindService_common(
@@ -253,6 +257,7 @@ public class ComponentAliasServiceTest {
assertThat(m.getMethodName()).isEqualTo("onDestroy");
// Note onServiceDisconnected() won't be called in this case.
receiver.ensureNoMoreMessages();
}
}
@@ -275,4 +280,43 @@ public class ComponentAliasServiceTest {
new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02"),
new ComponentName(SUB2_PACKAGE, APP_PACKAGE + ".s.Target02"));
}
@Test
public void testBindService_serviceKilled() throws Exception {
Intent originalIntent = new Intent().setPackage(APP_PACKAGE);
originalIntent.setAction(APP_PACKAGE + ".IS_ALIAS_02");
final ComponentName componentNameForClient =
new ComponentName(APP_PACKAGE, APP_PACKAGE + ".s.Alias02");
final ComponentName componentNameForTarget =
new ComponentName(SUB2_PACKAGE, APP_PACKAGE + ".s.Target02");
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");
m = receiver.waitForNextMessage();
assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
assertThat(m.getComponent()).isEqualTo(componentNameForClient);
// Now kill the service process.
ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE);
// Check the target side behavior.
m = receiver.waitForNextMessage();
assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected");
assertThat(m.getComponent()).isEqualTo(componentNameForClient);
receiver.ensureNoMoreMessages();
}
}
}