diff --git a/binder/src/androidTest/AndroidManifest.xml b/binder/src/androidTest/AndroidManifest.xml index 5e0da12614..b6d7157441 100644 --- a/binder/src/androidTest/AndroidManifest.xml +++ b/binder/src/androidTest/AndroidManifest.xml @@ -8,7 +8,15 @@ - - + + + + + + + + + + diff --git a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java index cdab0c3556..985d5188a1 100644 --- a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java +++ b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java @@ -22,6 +22,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import android.content.Context; +import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; import androidx.test.core.app.ApplicationProvider; @@ -32,7 +33,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallOptions; import io.grpc.Channel; -import io.grpc.ClientCall; import io.grpc.ClientInterceptors; import io.grpc.ConnectivityState; import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; @@ -234,6 +234,18 @@ public final class BinderChannelSmokeTest { assertThat(doCall("Hello").get()).isEqualTo("Hello"); } + @Test + public void testConnectViaIntentFilter() throws Exception { + // Compare with the mapping in AndroidManifest.xml. + channel = + BinderChannelBuilder.forAddress( + AndroidComponentAddress.forBindIntent( + new Intent().setAction("action1").setPackage(appContext.getPackageName())), + appContext) + .build(); + assertThat(doCall("Hello").get()).isEqualTo("Hello"); + } + @Test public void testUncaughtServerException() throws Exception { // Use a poison parcelable to cause an unexpected Exception in the server's onTransact(). diff --git a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java index 90197ee838..2cad159f2e 100644 --- a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java +++ b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java @@ -23,30 +23,35 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import java.net.SocketAddress; +import javax.annotation.Nullable; /** * The target of an Android {@link android.app.Service} binding. * - *

Consists of a {@link ComponentName} reference to the Service and the action, data URI, type, - * and category set for an {@link Intent} used to bind to it. All together, these fields identify - * the {@link android.os.IBinder} that would be returned by some implementation of {@link - * android.app.Service#onBind(Intent)}. Indeed, the semantics of {@link #equals(Object)} match - * Android's internal equivalence relation for caching the result of calling this method. See Consists of an explicit {@link Intent} that identifies an {@link android.os.IBinder} returned + * by some Service's {@link android.app.Service#onBind(Intent)} method. You can specify that Service + * by {@link ComponentName} or let Android resolve it using the Intent's other fields (package, + * action, data URI, type and category set). See Bound Services Overview - * for more. + * and Intents and Intent + * Filters for more. * *

For convenience in the common case where a {@link android.app.Service} exposes just one {@link * android.os.IBinder} IPC interface, we provide default values for the binding {@link Intent} * fields, namely, an action of {@link ApiConstants#ACTION_BIND}, an empty category set and null * type and data URI. + * + *

The semantics of {@link #equals(Object)} are the same as {@link Intent#filterEquals(Intent)}. */ public final class AndroidComponentAddress extends SocketAddress { private static final long serialVersionUID = 0L; - private final Intent bindIntent; // An "explicit" Intent. In other words, getComponent() != null. + private final Intent bindIntent; // "Explicit", having either a component or package restriction. protected AndroidComponentAddress(Intent bindIntent) { - checkArgument(bindIntent.getComponent() != null, "Missing required component"); + checkArgument( + bindIntent.getComponent() != null || bindIntent.getPackage() != null, + "'bindIntent' must be explicit. Specify either a package or ComponentName."); this.bindIntent = bindIntent; } @@ -79,14 +84,19 @@ public final class AndroidComponentAddress extends SocketAddress { } /** - * Creates a new address that refers to intent's component and that uses the "filter - * matching" fields of intent as the binding {@link Intent}. + * Creates a new address that uses the "filter matching" fields of intent as the + * binding {@link Intent}. + * + *

intent must be "explicit", i.e. having either a target component ({@link + * Intent#getComponent()}) or package restriction ({@link Intent#getPackage()}). See Intents and Intent + * Filters for more. * *

A multi-tenant {@link android.app.Service} can call this from its {@link * android.app.Service#onBind(Intent)} method to locate an appropriate {@link io.grpc.Server} by * listening address. * - * @throws IllegalArgumentException if intent's component is null + * @throws IllegalArgumentException if 'intent' isn't "explicit" */ public static AndroidComponentAddress forBindIntent(Intent intent) { return new AndroidComponentAddress(intent.cloneFilter()); @@ -104,12 +114,26 @@ public final class AndroidComponentAddress extends SocketAddress { /** * Returns the Authority which is the package name of the target app. * - *

See {@link android.content.ComponentName}. + *

See {@link android.content.ComponentName} and {@link Intent#getPackage()}. */ public String getAuthority() { - return getComponent().getPackageName(); + return getPackage(); } + /** + * Returns the package target of the wrapped {@link Intent}, either from its package restriction + * or, if not present, its fully qualified {@link ComponentName}. + */ + public String getPackage() { + if (bindIntent.getPackage() != null) { + return bindIntent.getPackage(); + } else { + return bindIntent.getComponent().getPackageName(); + } + } + + /** Returns the {@link ComponentName} of this binding {@link Intent}, or null if one isn't set. */ + @Nullable public ComponentName getComponent() { return bindIntent.getComponent(); } @@ -131,7 +155,7 @@ public final class AndroidComponentAddress extends SocketAddress { Intent intentForUri = bindIntent; if (intentForUri.getPackage() == null) { // URI_ANDROID_APP_SCHEME requires an "explicit package name" which isn't set by any of our - // factory methods. Oddly, our explicit ComponentName is not enough. + // factory methods. Oddly, a ComponentName is not enough. intentForUri = intentForUri.cloneFilter().setPackage(getComponent().getPackageName()); } return intentForUri.toUri(URI_ANDROID_APP_SCHEME); diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java index cb2fe5be3e..c17fb01fc9 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -784,9 +784,7 @@ public abstract class BinderTransport Context sourceContext, AndroidComponentAddress targetAddress) { return InternalLogId.allocate( BinderClientTransport.class, - sourceContext.getClass().getSimpleName() - + "->" - + targetAddress.getComponent().toShortString()); + sourceContext.getClass().getSimpleName() + "->" + targetAddress); } private static Attributes buildClientAttributes( diff --git a/binder/src/test/java/io/grpc/binder/AndroidComponentAddressTest.java b/binder/src/test/java/io/grpc/binder/AndroidComponentAddressTest.java index 8c7bc83d21..6d7e53e5a1 100644 --- a/binder/src/test/java/io/grpc/binder/AndroidComponentAddressTest.java +++ b/binder/src/test/java/io/grpc/binder/AndroidComponentAddressTest.java @@ -49,6 +49,26 @@ public final class AndroidComponentAddressTest { assertThat(addr.getComponent()).isSameInstanceAs(hostComponent); } + @Test + public void testTargetPackageNullComponentName() { + AndroidComponentAddress addr = + AndroidComponentAddress.forBindIntent( + new Intent().setPackage("com.foo").setAction(ApiConstants.ACTION_BIND)); + assertThat(addr.getPackage()).isEqualTo("com.foo"); + assertThat(addr.getComponent()).isNull(); + } + + @Test + public void testTargetPackageNonNullComponentName() { + AndroidComponentAddress addr = + AndroidComponentAddress.forBindIntent( + new Intent() + .setComponent(new ComponentName("com.foo", "com.foo.BarService")) + .setPackage("com.foo") + .setAction(ApiConstants.ACTION_BIND)); + assertThat(addr.getPackage()).isEqualTo("com.foo"); + } + @Test public void testAsBindIntent() { Intent bindIntent =