AndroidComponentAddress includes a target UserHandle (#11670) · grpc/grpc-java@e58c998
@@ -18,10 +18,14 @@
18181919import static android.content.Intent.URI_ANDROID_APP_SCHEME;
2020import static com.google.common.base.Preconditions.checkArgument;
21+import static com.google.common.base.Preconditions.checkState;
21222223import android.content.ComponentName;
2324import android.content.Context;
2425import android.content.Intent;
26+import android.os.UserHandle;
27+import com.google.common.base.Objects;
28+import io.grpc.ExperimentalApi;
2529import java.net.SocketAddress;
2630import javax.annotation.Nullable;
2731@@ -41,18 +45,25 @@
4145 * fields, namely, an action of {@link ApiConstants#ACTION_BIND}, an empty category set and null
4246 * type and data URI.
4347 *
44- * <p>The semantics of {@link #equals(Object)} are the same as {@link Intent#filterEquals(Intent)}.
48+ * <p>Optionally contains a {@link UserHandle} that must be considered wherever the {@link Intent}
49+ * is evaluated.
50+ *
51+ * <p>{@link #equals(Object)} uses {@link Intent#filterEquals(Intent)} semantics to compare Intents.
4552 */
4653public final class AndroidComponentAddress extends SocketAddress {
4754private static final long serialVersionUID = 0L;
48554956private final Intent bindIntent; // "Explicit", having either a component or package restriction.
505751-protected AndroidComponentAddress(Intent bindIntent) {
58+@Nullable
59+private final UserHandle targetUser; // null means the same user that hosts this process.
60+61+protected AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
5262checkArgument(
5363bindIntent.getComponent() != null || bindIntent.getPackage() != null,
5464"'bindIntent' must be explicit. Specify either a package or ComponentName.");
5565this.bindIntent = bindIntent;
66+this.targetUser = targetUser;
5667 }
57685869/**
@@ -99,7 +110,7 @@ public static AndroidComponentAddress forRemoteComponent(
99110 * @throws IllegalArgumentException if 'intent' isn't "explicit"
100111 */
101112public static AndroidComponentAddress forBindIntent(Intent intent) {
102-return new AndroidComponentAddress(intent.cloneFilter());
113+return new AndroidComponentAddress(intent.cloneFilter(), null);
103114 }
104115105116/**
@@ -108,7 +119,7 @@ public static AndroidComponentAddress forBindIntent(Intent intent) {
108119 */
109120public static AndroidComponentAddress forComponent(ComponentName component) {
110121return new AndroidComponentAddress(
111-new Intent(ApiConstants.ACTION_BIND).setComponent(component));
122+new Intent(ApiConstants.ACTION_BIND).setComponent(component), null);
112123 }
113124114125/**
@@ -141,6 +152,9 @@ public ComponentName getComponent() {
141152/**
142153 * Returns this address as an explicit {@link Intent} suitable for passing to {@link
143154 * Context#bindService}.
155+ *
156+ * <p>NB: The returned Intent does not specify a target Android user. If {@link #getTargetUser()}
157+ * is non-null, {@link Context#bindServiceAsUser} should be called instead.
144158 */
145159public Intent asBindIntent() {
146160return bindIntent.cloneFilter(); // Intent is mutable so return a copy.
@@ -177,13 +191,77 @@ public int hashCode() {
177191public boolean equals(Object obj) {
178192if (obj instanceof AndroidComponentAddress) {
179193AndroidComponentAddress that = (AndroidComponentAddress) obj;
180-return bindIntent.filterEquals(that.bindIntent);
194+return bindIntent.filterEquals(that.bindIntent)
195+&& Objects.equal(this.targetUser, that.targetUser);
181196 }
182197return false;
183198 }
184199185200@Override
186201public String toString() {
187-return "AndroidComponentAddress[" + bindIntent + "]";
202+StringBuilder builder = new StringBuilder("AndroidComponentAddress[");
203+if (targetUser != null) {
204+builder.append(targetUser);
205+builder.append("@");
206+ }
207+builder.append(bindIntent);
208+builder.append("]");
209+return builder.toString();
210+ }
211+212+/**
213+ * Identifies the Android user in which the bind Intent will be evaluated.
214+ *
215+ * <p>Returns the {@link UserHandle}, or null which means that the Android user hosting the
216+ * current process will be used.
217+ */
218+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
219+@Nullable
220+public UserHandle getTargetUser() {
221+return targetUser;
222+ }
223+224+public static Builder newBuilder() {
225+return new Builder();
226+ }
227+228+/** Fluently builds instances of {@link AndroidComponentAddress}. */
229+public static class Builder {
230+Intent bindIntent;
231+UserHandle targetUser;
232+233+/**
234+ * Sets the binding {@link Intent} to one having the "filter matching" fields of 'intent'.
235+ *
236+ * <p>'intent' must be "explicit", i.e. having either a target component ({@link
237+ * Intent#getComponent()}) or package restriction ({@link Intent#getPackage()}).
238+ */
239+public Builder setBindIntent(Intent intent) {
240+this.bindIntent = intent.cloneFilter();
241+return this;
242+ }
243+244+/**
245+ * Sets the binding {@link Intent} to one with the specified 'component' and default values for
246+ * all other fields, for convenience.
247+ */
248+public Builder setBindIntentFromComponent(ComponentName component) {
249+this.bindIntent = new Intent(ApiConstants.ACTION_BIND).setComponent(component);
250+return this;
251+ }
252+253+/** See {@link AndroidComponentAddress#getTargetUser()}. */
254+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
255+public Builder setTargetUser(@Nullable UserHandle targetUser) {
256+this.targetUser = targetUser;
257+return this;
258+ }
259+260+public AndroidComponentAddress build() {
261+// We clone any incoming mutable intent in the setter, not here. AndroidComponentAddress
262+// itself is immutable so multiple instances built from here can safely share 'bindIntent'.
263+checkState(bindIntent != null, "Required property 'bindIntent' unset");
264+return new AndroidComponentAddress(bindIntent, targetUser);
265+ }
188266 }
189267}