|
37 | 37 | import io.grpc.StatusException; |
38 | 38 | import io.grpc.binder.BinderChannelCredentials; |
39 | 39 | import java.lang.reflect.Method; |
| 40 | +import java.util.List; |
40 | 41 | import java.util.concurrent.Executor; |
41 | 42 | import java.util.logging.Level; |
42 | 43 | import java.util.logging.Logger; |
@@ -91,6 +92,8 @@ public String methodName() { |
91 | 92 | private final Observer observer; |
92 | 93 | private final Executor mainThreadExecutor; |
93 | 94 |
|
| 95 | + private static volatile Method queryIntentServicesAsUserMethod; |
| 96 | + |
94 | 97 | @GuardedBy("this") |
95 | 98 | private State state; |
96 | 99 |
|
@@ -253,19 +256,41 @@ void unbindInternal(Status reason) { |
253 | 256 | } |
254 | 257 | } |
255 | 258 |
|
256 | | - private static int getIdentifier(UserHandle userHandle) throws ReflectiveOperationException { |
257 | | - return (int) userHandle.getClass().getDeclaredMethod("getIdentifier").invoke(userHandle); |
| 259 | + // Sadly the PackageManager#resolveServiceAsUser() API we need isn't part of the SDK or even a |
| 260 | + // @SystemApi as of this writing. Modern Android prevents even system apps from calling it, by any |
| 261 | + // means (https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces). |
| 262 | + // So instead we call queryIntentServicesAsUser(), which does more than we need but *is* a |
| 263 | + // @SystemApi in all the SDK versions where we support cross-user Channels. |
| 264 | + @Nullable |
| 265 | + private static ResolveInfo resolveServiceAsUser( |
| 266 | + PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) { |
| 267 | + List<ResolveInfo> results = |
| 268 | + queryIntentServicesAsUser(packageManager, intent, flags, targetUserHandle); |
| 269 | + // The first query result is "what would be returned by resolveService", per the javadoc. |
| 270 | + return (results != null && !results.isEmpty()) ? results.get(0) : null; |
258 | 271 | } |
259 | 272 |
|
260 | | - // Sadly we must call this system API reflectively since it isn't part of the Android SDK. |
261 | | - private static ResolveInfo resolveServiceAsUser( |
| 273 | + // The cross-user Channel feature requires the client to be a system app so we assume @SystemApi |
| 274 | + // queryIntentServicesAsUser() is visible to us at runtime. It would be visible at build time too, |
| 275 | + // if our host system app were written to call it directly. We only have to use reflection here |
| 276 | + // because grpc-java is a library built outside the Android source tree where the compiler can't |
| 277 | + // see the "non-SDK" @SystemApis that we need. |
| 278 | + @Nullable |
| 279 | + @SuppressWarnings("unchecked") // Safe by PackageManager#queryIntentServicesAsUser spec in AOSP. |
| 280 | + private static List<ResolveInfo> queryIntentServicesAsUser( |
262 | 281 | PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) { |
263 | 282 | try { |
264 | | - return (ResolveInfo) |
265 | | - packageManager |
266 | | - .getClass() |
267 | | - .getMethod("resolveServiceAsUser", Intent.class, int.class, int.class) |
268 | | - .invoke(packageManager, intent, flags, getIdentifier(targetUserHandle)); |
| 283 | + if (queryIntentServicesAsUserMethod == null) { |
| 284 | + synchronized (ServiceBinding.class) { |
| 285 | + if (queryIntentServicesAsUserMethod == null) { |
| 286 | + queryIntentServicesAsUserMethod = |
| 287 | + PackageManager.class.getMethod( |
| 288 | + "queryIntentServicesAsUser", Intent.class, int.class, UserHandle.class); |
| 289 | + } |
| 290 | + } |
| 291 | + } |
| 292 | + return (List<ResolveInfo>) |
| 293 | + queryIntentServicesAsUserMethod.invoke(packageManager, intent, flags, targetUserHandle); |
269 | 294 | } catch (ReflectiveOperationException e) { |
270 | 295 | throw new VerifyException(e); |
271 | 296 | } |
|
0 commit comments