|
23 | 23 | import android.content.Context; |
24 | 24 | import android.content.Intent; |
25 | 25 | import android.content.ServiceConnection; |
| 26 | +import android.content.pm.PackageManager; |
| 27 | +import android.content.pm.ResolveInfo; |
| 28 | +import android.content.pm.ServiceInfo; |
26 | 29 | import android.os.Build; |
27 | 30 | import android.os.IBinder; |
28 | 31 | import android.os.UserHandle; |
29 | 32 | import androidx.annotation.AnyThread; |
30 | 33 | import androidx.annotation.MainThread; |
31 | 34 | import com.google.common.annotations.VisibleForTesting; |
| 35 | +import com.google.common.base.VerifyException; |
32 | 36 | import com.google.errorprone.annotations.concurrent.GuardedBy; |
33 | 37 | import io.grpc.Status; |
| 38 | +import io.grpc.StatusException; |
34 | 39 | import io.grpc.binder.BinderChannelCredentials; |
| 40 | +import java.lang.reflect.Method; |
| 41 | +import java.util.List; |
35 | 42 | import java.util.concurrent.Executor; |
36 | 43 | import java.util.logging.Level; |
37 | 44 | import java.util.logging.Logger; |
@@ -86,6 +93,8 @@ public String methodName() { |
86 | 93 | private final Observer observer; |
87 | 94 | private final Executor mainThreadExecutor; |
88 | 95 |
|
| 96 | + private static volatile Method queryIntentServicesAsUserMethod; |
| 97 | + |
89 | 98 | @GuardedBy("this") |
90 | 99 | private State state; |
91 | 100 |
|
@@ -252,6 +261,71 @@ void unbindInternal(Status reason) { |
252 | 261 | } |
253 | 262 | } |
254 | 263 |
|
| 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 | + |
255 | 329 | @MainThread |
256 | 330 | private void clearReferences() { |
257 | 331 | sourceContext = null; |
|
0 commit comments