Skip to content

Commit

Permalink
Enable indirect addressing using <intent-filter>s. (grpc#10550)
Browse files Browse the repository at this point in the history
AndroidComponentAddress now accepts an Intent with merely a package
restriction, not a full ComponentName. This lets clients avoid hard
coding Service class names that they don't control.

Fixes grpc#9062
  • Loading branch information
jdcormie authored and DNVindhya committed Oct 5, 2023
1 parent c02db51 commit d0dcf17
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 20 deletions.
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

0 comments on commit d0dcf17

Please sign in to comment.