mirror of https://github.com/grpc/grpc-java.git
Enable indirect addressing using <intent-filter>s. (#10550)
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 #9062
This commit is contained in:
parent
5f480de2ee
commit
134b0490d5
|
|
@ -8,7 +8,15 @@
|
||||||
<application android:debuggable="true">
|
<application android:debuggable="true">
|
||||||
<uses-library android:name="android.test.runner" />
|
<uses-library android:name="android.test.runner" />
|
||||||
|
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService1" />
|
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService2" />
|
<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>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
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 com.google.common.util.concurrent.SettableFuture;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
import io.grpc.Channel;
|
import io.grpc.Channel;
|
||||||
import io.grpc.ClientCall;
|
|
||||||
import io.grpc.ClientInterceptors;
|
import io.grpc.ClientInterceptors;
|
||||||
import io.grpc.ConnectivityState;
|
import io.grpc.ConnectivityState;
|
||||||
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
||||||
|
|
@ -234,6 +234,18 @@ public final class BinderChannelSmokeTest {
|
||||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
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
|
@Test
|
||||||
public void testUncaughtServerException() throws Exception {
|
public void testUncaughtServerException() throws Exception {
|
||||||
// Use a poison parcelable to cause an unexpected Exception in the server's onTransact().
|
// Use a poison parcelable to cause an unexpected Exception in the server's onTransact().
|
||||||
|
|
|
||||||
|
|
@ -23,30 +23,35 @@ import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The target of an Android {@link android.app.Service} binding.
|
* 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,
|
* <p>Consists of an explicit {@link Intent} that identifies an {@link android.os.IBinder} returned
|
||||||
* and category set for an {@link Intent} used to bind to it. All together, these fields identify
|
* by some Service's {@link android.app.Service#onBind(Intent)} method. You can specify that Service
|
||||||
* the {@link android.os.IBinder} that would be returned by some implementation of {@link
|
* by {@link ComponentName} or let Android resolve it using the Intent's other fields (package,
|
||||||
* android.app.Service#onBind(Intent)}. Indeed, the semantics of {@link #equals(Object)} match
|
* action, data URI, type and category set). See <a
|
||||||
* Android's internal equivalence relation for caching the result of calling this method. See <a
|
|
||||||
* href="https://developer.android.com/guide/components/bound-services">Bound Services Overview</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
|
* <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}
|
* 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
|
* fields, namely, an action of {@link ApiConstants#ACTION_BIND}, an empty category set and null
|
||||||
* type and data URI.
|
* 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 {
|
public final class AndroidComponentAddress extends SocketAddress {
|
||||||
private static final long serialVersionUID = 0L;
|
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) {
|
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;
|
this.bindIntent = bindIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,14 +84,19 @@ public final class AndroidComponentAddress extends SocketAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new address that refers to <code>intent</code>'s component and that uses the "filter
|
* Creates a new address that uses the "filter matching" fields of <code>intent</code> as the
|
||||||
* matching" fields of <code>intent</code> as the binding {@link Intent}.
|
* 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
|
* <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
|
* android.app.Service#onBind(Intent)} method to locate an appropriate {@link io.grpc.Server} by
|
||||||
* listening address.
|
* listening address.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if intent's component is null
|
* @throws IllegalArgumentException if 'intent' isn't "explicit"
|
||||||
*/
|
*/
|
||||||
public static AndroidComponentAddress forBindIntent(Intent intent) {
|
public static AndroidComponentAddress forBindIntent(Intent intent) {
|
||||||
return new AndroidComponentAddress(intent.cloneFilter());
|
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.
|
* 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() {
|
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() {
|
public ComponentName getComponent() {
|
||||||
return bindIntent.getComponent();
|
return bindIntent.getComponent();
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +155,7 @@ public final class AndroidComponentAddress extends SocketAddress {
|
||||||
Intent intentForUri = bindIntent;
|
Intent intentForUri = bindIntent;
|
||||||
if (intentForUri.getPackage() == null) {
|
if (intentForUri.getPackage() == null) {
|
||||||
// URI_ANDROID_APP_SCHEME requires an "explicit package name" which isn't set by any of our
|
// 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());
|
intentForUri = intentForUri.cloneFilter().setPackage(getComponent().getPackageName());
|
||||||
}
|
}
|
||||||
return intentForUri.toUri(URI_ANDROID_APP_SCHEME);
|
return intentForUri.toUri(URI_ANDROID_APP_SCHEME);
|
||||||
|
|
|
||||||
|
|
@ -784,9 +784,7 @@ public abstract class BinderTransport
|
||||||
Context sourceContext, AndroidComponentAddress targetAddress) {
|
Context sourceContext, AndroidComponentAddress targetAddress) {
|
||||||
return InternalLogId.allocate(
|
return InternalLogId.allocate(
|
||||||
BinderClientTransport.class,
|
BinderClientTransport.class,
|
||||||
sourceContext.getClass().getSimpleName()
|
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
|
||||||
+ "->"
|
|
||||||
+ targetAddress.getComponent().toShortString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Attributes buildClientAttributes(
|
private static Attributes buildClientAttributes(
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,26 @@ public final class AndroidComponentAddressTest {
|
||||||
assertThat(addr.getComponent()).isSameInstanceAs(hostComponent);
|
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
|
@Test
|
||||||
public void testAsBindIntent() {
|
public void testAsBindIntent() {
|
||||||
Intent bindIntent =
|
Intent bindIntent =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue