Skip to content

Commit 1655cb4

Browse files
committed
Android: fixed internal review points
1 parent 65fab31 commit 1655cb4

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package io.grpc.binder;
1818

1919
import static android.content.Intent.URI_ANDROID_APP_SCHEME;
20+
import static android.content.Intent.URI_INTENT_SCHEME;
2021
import static com.google.common.base.Preconditions.checkArgument;
2122
import static com.google.common.base.Preconditions.checkState;
2223

2324
import android.annotation.SuppressLint;
2425
import android.content.ComponentName;
2526
import android.content.Context;
2627
import android.content.Intent;
28+
import android.os.Build;
2729
import android.os.UserHandle;
2830
import com.google.common.base.Objects;
2931
import io.grpc.ExperimentalApi;
@@ -166,15 +168,17 @@ public Intent asBindIntent() {
166168
*
167169
* <p>See {@link Intent#URI_ANDROID_APP_SCHEME} for details.
168170
*/
169-
@SuppressLint("InlinedApi")
170171
public String asAndroidAppUri() {
171172
Intent intentForUri = bindIntent;
172173
if (intentForUri.getPackage() == null) {
173174
// URI_ANDROID_APP_SCHEME requires an "explicit package name" which isn't set by any of our
174175
// factory methods. Oddly, a ComponentName is not enough.
175176
intentForUri = intentForUri.cloneFilter().setPackage(getComponent().getPackageName());
176177
}
177-
return intentForUri.toUri(URI_ANDROID_APP_SCHEME);
178+
return intentForUri.toUri(
179+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1
180+
? URI_ANDROID_APP_SCHEME
181+
: URI_INTENT_SCHEME);
178182
}
179183

180184
@Override

binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,22 @@
2323
import android.content.Context;
2424
import android.content.Intent;
2525
import android.content.ServiceConnection;
26+
import android.content.pm.PackageManager;
27+
import android.content.pm.ResolveInfo;
28+
import android.content.pm.ServiceInfo;
2629
import android.os.Build;
2730
import android.os.IBinder;
2831
import android.os.UserHandle;
2932
import androidx.annotation.AnyThread;
3033
import androidx.annotation.MainThread;
3134
import com.google.common.annotations.VisibleForTesting;
35+
import com.google.common.base.VerifyException;
3236
import com.google.errorprone.annotations.concurrent.GuardedBy;
3337
import io.grpc.Status;
38+
import io.grpc.StatusException;
3439
import io.grpc.binder.BinderChannelCredentials;
40+
import java.lang.reflect.Method;
41+
import java.util.List;
3542
import java.util.concurrent.Executor;
3643
import java.util.logging.Level;
3744
import java.util.logging.Logger;
@@ -86,6 +93,8 @@ public String methodName() {
8693
private final Observer observer;
8794
private final Executor mainThreadExecutor;
8895

96+
private static volatile Method queryIntentServicesAsUserMethod;
97+
8998
@GuardedBy("this")
9099
private State state;
91100

@@ -252,6 +261,71 @@ void unbindInternal(Status reason) {
252261
}
253262
}
254263

264+
// Sadly the PackageManager#resolveServiceAsUser() API we need isn't part of the SDK or even a
265+
// @SystemApi as of this writing. Modern Android prevents even system apps from calling it, by any
266+
// means (https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces).
267+
// So instead we call queryIntentServicesAsUser(), which does more than we need but *is* a
268+
// @SystemApi in all the SDK versions where we support cross-user Channels.
269+
@Nullable
270+
private static ResolveInfo resolveServiceAsUser(
271+
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
272+
List<ResolveInfo> results =
273+
queryIntentServicesAsUser(packageManager, intent, flags, targetUserHandle);
274+
// The first query result is "what would be returned by resolveService", per the javadoc.
275+
return (results != null && !results.isEmpty()) ? results.get(0) : null;
276+
}
277+
278+
// The cross-user Channel feature requires the client to be a system app so we assume @SystemApi
279+
// queryIntentServicesAsUser() is visible to us at runtime. It would be visible at build time too,
280+
// if our host system app were written to call it directly. We only have to use reflection here
281+
// because grpc-java is a library built outside the Android source tree where the compiler can't
282+
// see the "non-SDK" @SystemApis that we need.
283+
@Nullable
284+
@SuppressWarnings("unchecked") // Safe by PackageManager#queryIntentServicesAsUser spec in AOSP.
285+
private static List<ResolveInfo> queryIntentServicesAsUser(
286+
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
287+
try {
288+
if (queryIntentServicesAsUserMethod == null) {
289+
synchronized (ServiceBinding.class) {
290+
if (queryIntentServicesAsUserMethod == null) {
291+
queryIntentServicesAsUserMethod =
292+
PackageManager.class.getMethod(
293+
"queryIntentServicesAsUser", Intent.class, int.class, UserHandle.class);
294+
}
295+
}
296+
}
297+
return (List<ResolveInfo>)
298+
queryIntentServicesAsUserMethod.invoke(packageManager, intent, flags, targetUserHandle);
299+
} catch (ReflectiveOperationException e) {
300+
throw new VerifyException(e);
301+
}
302+
}
303+
304+
@AnyThread
305+
@Override
306+
public ServiceInfo resolve() throws StatusException {
307+
checkState(sourceContext != null);
308+
PackageManager packageManager = sourceContext.getPackageManager();
309+
int flags = 0;
310+
if (Build.VERSION.SDK_INT >= 29) {
311+
// Filter out non-'directBootAware' <service>s when 'targetUserHandle' is locked. Here's why:
312+
// Callers want 'bindIntent' to #resolve() to the same thing a follow-up call to #bind() will.
313+
// But bindService() *always* ignores services that can't presently be created for lack of
314+
// 'directBootAware'-ness. This flag explicitly tells resolveService() to act the same way.
315+
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
316+
}
317+
ResolveInfo resolveInfo =
318+
targetUserHandle != null
319+
? resolveServiceAsUser(packageManager, bindIntent, flags, targetUserHandle)
320+
: packageManager.resolveService(bindIntent, flags);
321+
if (resolveInfo == null) {
322+
throw Status.UNIMPLEMENTED // Same status code as when bindService() returns false.
323+
.withDescription("resolveService(" + bindIntent + " / " + targetUserHandle + ") was null")
324+
.asException();
325+
}
326+
return resolveInfo.serviceInfo;
327+
}
328+
255329
@MainThread
256330
private void clearReferences() {
257331
sourceContext = null;

0 commit comments

Comments
 (0)