1 /* 2 * Copyright 2020 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.binder; 18 19 import static android.content.Intent.URI_ANDROID_APP_SCHEME; 20 import static com.google.common.base.Preconditions.checkArgument; 21 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import java.net.SocketAddress; 26 27 /** 28 * The target of an Android {@link android.app.Service} binding. 29 * 30 * <p>Consists of a {@link ComponentName} reference to the Service and the action, data URI, type, 31 * and category set for an {@link Intent} used to bind to it. All together, these fields identify 32 * the {@link android.os.IBinder} that would be returned by some implementation of {@link 33 * android.app.Service#onBind(Intent)}. Indeed, the semantics of {@link #equals(Object)} match 34 * Android's internal equivalence relation for caching the result of calling this method. See <a 35 * href="https://developer.android.com/guide/components/bound-services">Bound Services Overview</a> 36 * for more. 37 * 38 * <p>For convenience in the common case where a {@link android.app.Service} exposes just one {@link 39 * android.os.IBinder} IPC interface, we provide default values for the binding {@link Intent} 40 * fields, namely, an action of {@link ApiConstants#ACTION_BIND}, an empty category set and null 41 * type and data URI. 42 */ 43 public final class AndroidComponentAddress extends SocketAddress { 44 private static final long serialVersionUID = 0L; 45 46 private final Intent bindIntent; // An "explicit" Intent. In other words, getComponent() != null. 47 AndroidComponentAddress(Intent bindIntent)48 protected AndroidComponentAddress(Intent bindIntent) { 49 checkArgument(bindIntent.getComponent() != null, "Missing required component"); 50 this.bindIntent = bindIntent; 51 } 52 53 /** 54 * Creates an address for the given {@link android.app.Service} instance with the default binding 55 * {@link Intent}. 56 */ forContext(Context context)57 public static AndroidComponentAddress forContext(Context context) { 58 return forLocalComponent(context, context.getClass()); 59 } 60 61 /** 62 * Creates an address referencing a {@link android.app.Service} hosted by this application and 63 * using the default binding {@link Intent}. 64 */ forLocalComponent(Context context, Class<?> cls)65 public static AndroidComponentAddress forLocalComponent(Context context, Class<?> cls) { 66 return forComponent(new ComponentName(context, cls)); 67 } 68 69 /** 70 * Creates an address referencing a {@link android.app.Service} in another 71 * application and using the default binding {@link Intent}. 72 * 73 * @param applicationPackage The package name of the application containing the server. 74 * @param serviceClassName The full class name of the Android Service to bind to. 75 */ forRemoteComponent( String applicationPackage, String serviceClassName)76 public static AndroidComponentAddress forRemoteComponent( 77 String applicationPackage, String serviceClassName) { 78 return forComponent(new ComponentName(applicationPackage, serviceClassName)); 79 } 80 81 /** 82 * Creates a new address that refers to <code>intent</code>'s component and that uses the "filter 83 * matching" fields of <code>intent</code> as the binding {@link Intent}. 84 * 85 * <p>A multi-tenant {@link android.app.Service} can call this from its {@link 86 * android.app.Service#onBind(Intent)} method to locate an appropriate {@link io.grpc.Server} by 87 * listening address. 88 * 89 * @throws IllegalArgumentException if intent's component is null 90 */ forBindIntent(Intent intent)91 public static AndroidComponentAddress forBindIntent(Intent intent) { 92 return new AndroidComponentAddress(intent.cloneFilter()); 93 } 94 95 /** 96 * Creates an address referencing the specified {@link android.app.Service} component and using 97 * the default binding {@link Intent}. 98 */ forComponent(ComponentName component)99 public static AndroidComponentAddress forComponent(ComponentName component) { 100 return new AndroidComponentAddress( 101 new Intent(ApiConstants.ACTION_BIND).setComponent(component)); 102 } 103 104 /** 105 * Returns the Authority which is the package name of the target app. 106 * 107 * <p>See {@link android.content.ComponentName}. 108 */ getAuthority()109 public String getAuthority() { 110 return getComponent().getPackageName(); 111 } 112 getComponent()113 public ComponentName getComponent() { 114 return bindIntent.getComponent(); 115 } 116 117 /** 118 * Returns this address as an explicit {@link Intent} suitable for passing to {@link 119 * Context#bindService}. 120 */ asBindIntent()121 public Intent asBindIntent() { 122 return bindIntent.cloneFilter(); // Intent is mutable so return a copy. 123 } 124 125 /** 126 * Returns this address as an "android-app://" uri. 127 * 128 * <p>See {@link Intent#URI_ANDROID_APP_SCHEME} for details. 129 */ asAndroidAppUri()130 public String asAndroidAppUri() { 131 Intent intentForUri = bindIntent; 132 if (intentForUri.getPackage() == null) { 133 // URI_ANDROID_APP_SCHEME requires an "explicit package name" which isn't set by any of our 134 // factory methods. Oddly, our explicit ComponentName is not enough. 135 intentForUri = intentForUri.cloneFilter().setPackage(getComponent().getPackageName()); 136 } 137 return intentForUri.toUri(URI_ANDROID_APP_SCHEME); 138 } 139 140 @Override hashCode()141 public int hashCode() { 142 Intent intentForHashCode = bindIntent; 143 // Clear a (usually redundant) package filter to work around an Android >= 31 bug where certain 144 // Intents compare filterEquals() but have different filterHashCode() values. It's always safe 145 // to include fewer fields in the hashCode() computation. 146 if (intentForHashCode.getPackage() != null) { 147 intentForHashCode = intentForHashCode.cloneFilter().setPackage(null); 148 } 149 return intentForHashCode.filterHashCode(); 150 } 151 152 @Override equals(Object obj)153 public boolean equals(Object obj) { 154 if (obj instanceof AndroidComponentAddress) { 155 AndroidComponentAddress that = (AndroidComponentAddress) obj; 156 return bindIntent.filterEquals(that.bindIntent); 157 } 158 return false; 159 } 160 161 @Override toString()162 public String toString() { 163 return "AndroidComponentAddress[" + bindIntent + "]"; 164 } 165 } 166