{"record":{"id":"1d56d0fdd2ad755a","repo":"grpc/grpc-java","slug":"target-android-user-nameresolver-arg-requires-sdk","errorCode":null,"errorMessage":"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility","messagePattern":"TARGET_ANDROID_USER NameResolver\\.Arg requires SDK_INT >= R and @SystemApi visibility","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"binder/src/main/java/io/grpc/binder/internal/IntentNameResolver.java","lineNumber":104,"sourceCode":"    Context context =\n        checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), \"SOURCE_ANDROID_CONTEXT\")\n            .getApplicationContext();\n    this.targetUserContext =\n        targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;\n    // This Executor is nominally optional but all grpc-java Channels provide it since 1.25.\n    this.offloadExecutor =\n        checkNotNull(args.getOffloadExecutor(), \"NameResolver.Args.getOffloadExecutor()\");\n    // Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.\n    this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);\n    this.syncContext = args.getSynchronizationContext();\n    this.serviceConfigParser = args.getServiceConfigParser();\n  }\n\n  private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {\n    try {\n      return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.\n    } catch (ReflectiveOperationException e) {\n      throw new IllegalArgumentException(\n          \"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility\");\n    }\n  }\n\n  @Override\n  public void start(Listener2 listener) {\n    checkState(this.listener == null, \"Already started!\");\n    checkState(!shutdown, \"Resolver is shutdown\");\n    this.listener = checkNotNull(listener);\n    sequentialExecutor.execute(this::registerReceiver);\n    resolve();\n  }\n\n  @Override\n  public void refresh() {\n    checkState(listener != null, \"Not started!\");\n    resolve();\n  }","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/binder/src/main/java/io/grpc/binder/internal/IntentNameResolver.java#L86-L122","documentation":"IntentNameResolver resolves targets in a different Android user profile via TARGET_ANDROID_USER NameResolver.Arg. Resolving a target user requires Context.createContextAsUser, which is only available as @SystemApi from Android SDK_INT >= R (30). When reflection to invoke that hidden API fails, the resolver throws this IllegalArgumentException rather than silently ignoring the target user.","triggerScenarios":"Using IntentNameResolver with a NameResolver.Arg targeting a different Android user on a device running SDK_INT < 30 (pre-R), or on a build where the app lacks @SystemApi / system-privilege visibility so createContextAsUser throws ReflectiveOperationException.","commonSituations":"Running a cross-user gRPC-binder app on Android 10 or older; shipping a consumer (non-system / non-privileged) APK that attempts TARGET_ANDROID_USER resolution; ROM or SDK-version changes that remove API visibility.","solutions":["Only set a TARGET_ANDROID_USER arg when Build.VERSION.SDK_INT >= Build.VERSION_CODES.R and the app has system/privileged visibility.","Fall back to resolving within the current user (omit the target-user arg) when the platform requirement is unmet.","If cross-user is required, run on API 30+ with a privileged (system-signed, PRIVILEGED_SIGNATURE) install."],"exampleFix":"// before\nresolverArgsBuilder.setNameResolverArg(IntentNameResolver.TARGET_ANDROID_USER, otherUserHandle);\n// after\nif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && isPrivilegedApp()) {\n  resolverArgsBuilder.setNameResolverArg(IntentNameResolver.TARGET_ANDROID_USER, otherUserHandle);\n} else {\n  // resolve in current user\n}","handlingStrategy":"validation","validationCode":"if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !isSystemVisible()) {\n  throw new IllegalStateException(\"TARGET_ANDROID_USER requires SDK_INT >= R and @SystemApi visibility\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  resolver.start(listener);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"TARGET_ANDROID_USER\")) { /* fall back to current user resolution */ }\n}","preventionTips":["Gate TARGET_ANDROID_USER usage on Build.VERSION.SDK_INT >= 30.","Only use in system/privileged apps; check signing/privileges at startup.","Provide a same-user fallback resolution path."],"tags":["android","binder","system-api","sdk-version"],"backgroundTag":"unsupported-platform","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}