Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable indirect addressing using <intent-filter>s. #10550

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions binder/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />

<service android:name="io.grpc.binder.HostServices$HostService1" />
<service android:name="io.grpc.binder.HostServices$HostService2" />
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
<intent-filter>
<action android:name="action1"/>
</intent-filter>
</service>
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
<intent-filter>
<action android:name="action2"/>
</intent-filter>
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -32,7 +33,6 @@
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;
Expand Down Expand Up @@ -234,6 +234,18 @@ public void testConnectViaTargetUri() throws Exception {
assertThat(doCall("Hello").get()).isEqualTo("Hello");
}

@Test
public void testConnectViaIntentFilter() throws Exception {
// Compare with the <intent-filter> 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().
Expand Down
52 changes: 38 additions & 14 deletions binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,35 @@
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.
*
* <p>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 <a
* <p>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 <a
* href="https://developer.android.com/guide/components/bound-services">Bound Services Overview</a>
* for more.
* and <a href="https://developer.android.com/guide/components/intents-filters">Intents and Intent
* Filters</a> for more.
*
* <p>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.
*
* <p>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;
}

Expand Down Expand Up @@ -79,14 +84,19 @@ public static AndroidComponentAddress forRemoteComponent(
}

/**
* Creates a new address that refers to <code>intent</code>'s component and that uses the "filter
* matching" fields of <code>intent</code> as the binding {@link Intent}.
* Creates a new address that uses the "filter matching" fields of <code>intent</code> as the
* binding {@link Intent}.
*
* <p><code>intent</code> must be "explicit", i.e. having either a target component ({@link
* Intent#getComponent()}) or package restriction ({@link Intent#getPackage()}). See <a
* href="https://developer.android.com/guide/components/intents-filters">Intents and Intent
* Filters</a> for more.
*
* <p>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());
Expand All @@ -104,12 +114,26 @@ public static AndroidComponentAddress forComponent(ComponentName component) {
/**
* Returns the Authority which is the package name of the target app.
*
* <p>See {@link android.content.ComponentName}.
* <p>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();
}
Expand All @@ -131,7 +155,7 @@ public String asAndroidAppUri() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,9 +784,7 @@ private static InternalLogId buildLogId(
Context sourceContext, AndroidComponentAddress targetAddress) {
return InternalLogId.allocate(
BinderClientTransport.class,
sourceContext.getClass().getSimpleName()
+ "->"
+ targetAddress.getComponent().toShortString());
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
}

private static Attributes buildClientAttributes(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ public void testComponent() {
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 =
Expand Down