summaryrefslogtreecommitdiff
path: root/core/java/android/content/PermissionChecker.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/content/PermissionChecker.java')
-rw-r--r--core/java/android/content/PermissionChecker.java502
1 files changed, 408 insertions, 94 deletions
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 66e088359459..5089f30585b4 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,19 +16,21 @@
package android.content;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.permission.IPermissionChecker;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* This class provides permission check APIs that verify both the
@@ -70,44 +72,34 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
public final class PermissionChecker {
- /**
- * The permission is granted.
- *
- * @hide
- */
- public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+ private static final String LOG_TAG = PermissionChecker.class.getName();
- /**
- * The permission is denied. Applicable only to runtime and app op permissions.
- *
- * <p>Returned when:
- * <ul>
- * <li>the runtime permission is granted, but the corresponding app op is denied
- * for runtime permissions.</li>
- * <li>the app ops is ignored for app op permissions.</li>
- * </ul>
- *
- * @hide
- */
- public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+ private static final String PLATFORM_PACKAGE_NAME = "android";
- /**
- * The permission is denied.
- *
- * <p>Returned when:
+ /** The permission is granted. */
+ public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
+
+ /** Only for runtime permissions, its returned when the runtime permission
+ * is granted, but the corresponding app op is denied. */
+ public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED;
+
+ /** Returned when:
* <ul>
- * <li>the permission is denied for non app op permissions.</li>
- * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
- * and permission is denied.</li>
+ * <li>For non app op permissions, returned when the permission is denied.</li>
+ * <li>For app op permissions, returned when the app op is denied or app op is
+ * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
* </ul>
*
- * @hide
*/
- public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+ public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED;
/** Constant when the PID for which we check permissions is unknown. */
public static final int PID_UNKNOWN = -1;
+ // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
+ private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
+ = new ConcurrentHashMap<>();
+
/** @hide */
@IntDef({PERMISSION_GRANTED,
PERMISSION_SOFT_DENIED,
@@ -115,8 +107,6 @@ public final class PermissionChecker {
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionResult {}
- private static volatile IPermissionChecker sService;
-
private PermissionChecker() {
/* do nothing */
}
@@ -242,7 +232,7 @@ public final class PermissionChecker {
public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
@NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
+ return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
message, false /*startDataDelivery*/, /*fromDatasource*/ true);
}
@@ -317,23 +307,21 @@ public final class PermissionChecker {
public static int checkPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery) {
- return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
+ return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
message, startDataDelivery, /*fromDatasource*/ false);
}
private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
- @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
// If the check failed in the middle of the chain, finish any started op.
- try {
- final int result = getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- startDataDelivery, fromDatasource);
- return result;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ final int result = checkPermissionCommon(context, permission, attributionSource,
+ message, true /*forDataDelivery*/, startDataDelivery, fromDatasource);
+ if (startDataDelivery && result != PERMISSION_GRANTED) {
+ finishDataDelivery(context, AppOpsManager.permissionToOp(permission),
+ attributionSource);
}
- return PERMISSION_HARD_DENIED;
+ return result;
}
/**
@@ -368,14 +356,9 @@ public final class PermissionChecker {
public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- /*startDataDelivery*/ true, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return checkPermissionCommon(context, permission, attributionSource,
+ message, true /*forDataDelivery*/, /*startDataDelivery*/ true,
+ /*fromDatasource*/ false);
}
/**
@@ -407,14 +390,13 @@ public final class PermissionChecker {
public static int startOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(
- AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
- true /*forDataDelivery*/, true /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, true /*forDataDelivery*/, true /*startDataDelivery*/);
+ // It is important to finish any started op if some step in the attribution chain failed.
+ if (result != PERMISSION_GRANTED) {
+ finishDataDelivery(context, opName, attributionSource);
}
- return PERMISSION_HARD_DENIED;
+ return result;
}
/**
@@ -430,10 +412,15 @@ public final class PermissionChecker {
*/
public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
@NonNull AttributionSource attributionSource) {
- try {
- getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ if (op == null || attributionSource.getPackageName() == null) {
+ return;
+ }
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ appOpsManager.finishProxyOp(op, attributionSource);
+
+ if (attributionSource.getNext() != null) {
+ finishDataDelivery(context, op, attributionSource.getNext());
}
}
@@ -469,14 +456,8 @@ public final class PermissionChecker {
public static int checkOpForPreflight(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, false /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, false /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -508,14 +489,8 @@ public final class PermissionChecker {
public static int checkOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, true /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, true /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -586,14 +561,9 @@ public final class PermissionChecker {
@PermissionResult
public static int checkPermissionForPreflight(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
- /*startDataDelivery*/ false, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return checkPermissionCommon(context, permission, attributionSource,
+ null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false,
+ /*fromDatasource*/ false);
}
/**
@@ -828,12 +798,356 @@ public final class PermissionChecker {
Binder.getCallingUid(), packageName);
}
- private static @NonNull IPermissionChecker getPermissionCheckerService() {
- // Race is fine, we may end up looking up the same instance twice, no big deal.
- if (sService == null) {
- final IBinder service = ServiceManager.getService("permission_checker");
- sService = IPermissionChecker.Stub.asInterface(service);
+ @PermissionResult
+ private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
+ @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+ boolean fromDatasource) {
+ PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
+
+ if (permissionInfo == null) {
+ try {
+ permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+ if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+ // Double addition due to concurrency is fine - the backing store is concurrent.
+ sPlatformPermissions.put(permission, permissionInfo);
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return PERMISSION_HARD_DENIED;
+ }
+ }
+
+ if (permissionInfo.isAppOp()) {
+ return checkAppOpPermission(context, permission, attributionSource, message,
+ forDataDelivery, fromDatasource);
+ }
+ if (permissionInfo.isRuntime()) {
+ return checkRuntimePermission(context, permission, attributionSource, message,
+ forDataDelivery, startDataDelivery, fromDatasource);
+ }
+
+ if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
+ attributionSource.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (attributionSource.getNext() != null) {
+ return checkPermissionCommon(context, permission,
+ attributionSource.getNext(), message, forDataDelivery,
+ startDataDelivery, /*fromDatasource*/ false);
+ }
+
+ return PERMISSION_GRANTED;
+ }
+
+ @PermissionResult
+ private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean fromDatasource) {
+ final int op = AppOpsManager.permissionToOpCode(permission);
+ if (op < 0) {
+ Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
+ return PERMISSION_HARD_DENIED;
+ }
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (fromDatasource || next != null);
+
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (!(fromDatasource && current == attributionSource)
+ && next != null && !current.isTrusted(context)) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ // The access is for oneself if this is the single receiver of data
+ // after the data source or if this is the single attribution source
+ // in the chain if not from a datasource.
+ final boolean singleReceiverFromDatasource = (fromDatasource
+ && current == attributionSource && next != null && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
+ selfAccess, singleReceiverFromDatasource);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_IGNORED:
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_DEFAULT: {
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ attributionSource.getUid(), attributionSource
+ .getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ // Now let's check the identity chain...
+ final int op = AppOpsManager.permissionToOpCode(permission);
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (fromDatasource || next != null);
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (!(fromDatasource && current == attributionSource)
+ && next != null && !current.isTrusted(context)) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ // If we already checked the permission for this one, skip the work
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ current.getUid(), current.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (op < 0) {
+ // Bg location is one-off runtime modifier permission and has no app op
+ if (sPlatformPermissions.contains(permission)
+ && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+ Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+ + " with no app op defined!");
+ }
+ if (next == null) {
+ return PERMISSION_GRANTED;
+ }
+ current = next;
+ continue;
+ }
+
+ // The access is for oneself if this is the single receiver of data
+ // after the data source or if this is the single attribution source
+ // in the chain if not from a datasource.
+ final boolean singleReceiverFromDatasource = (fromDatasource
+ && current == attributionSource && next != null && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ singleReceiverFromDatasource);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
+ int uid, @NonNull Set<String> renouncedPermissions) {
+ final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
+ uid) == PackageManager.PERMISSION_GRANTED;
+ if (permissionGranted && renouncedPermissions.contains(permission)
+ && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+ /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ return permissionGranted;
+ }
+
+ private static int checkOp(@NonNull Context context, @NonNull int op,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery) {
+ if (op < 0 || attributionSource.getPackageName() == null) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (next != null);
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (next != null && !current.isTrusted(context)) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ // The access is for oneself if this is the single attribution source in the chain.
+ final boolean selfAccess = (next == null);
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ /*fromDatasource*/ false);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static int performOpTransaction(@NonNull Context context, int op,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
+ boolean selfAccess, boolean singleReceiverFromDatasource) {
+ // We cannot perform app ops transactions without a package name. In all relevant
+ // places we pass the package name but just in case there is a bug somewhere we
+ // do a best effort to resolve the package from the UID (pick first without a loss
+ // of generality - they are in the same security sandbox).
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+ ? attributionSource : attributionSource.getNext();
+ if (!forDataDelivery) {
+ final String resolvedAccessorPackageName = resolvePackageName(context, accessorSource);
+ if (resolvedAccessorPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
+ accessorSource.getUid(), resolvedAccessorPackageName);
+ final AttributionSource next = accessorSource.getNext();
+ if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
+ final String resolvedNextPackageName = resolvePackageName(context, next);
+ if (resolvedNextPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
+ resolvedNextPackageName);
+ }
+ return opMode;
+ } else if (startDataDelivery) {
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ // If the datasource is not in a trusted platform component then in would not
+ // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+ // an app is exposing runtime permission protected data but cannot blame others
+ // in a trusted way which would not properly show in permission usage UIs.
+ // As a fallback we note a proxy op that blames the app and the datasource.
+ try {
+ return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ /*startIfModeDefault*/ false,
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+ + " platform defined runtime permission "
+ + AppOpsManager.opToPermission(op) + " while not having "
+ + Manifest.permission.UPDATE_APP_OPS_STATS);
+ return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ // If the datasource is not in a trusted platform component then in would not
+ // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+ // an app is exposing runtime permission protected data but cannot blame others
+ // in a trusted way which would not properly show in permission usage UIs.
+ // As a fallback we note a proxy op that blames the app and the datasource.
+ try {
+ return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+ + " platform defined runtime permission "
+ + AppOpsManager.opToPermission(op) + " while not having "
+ + Manifest.permission.UPDATE_APP_OPS_STATS);
+ return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ }
+ }
+
+ private static @Nullable String resolvePackageName(@NonNull Context context,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource.getPackageName();
+ }
+ final String[] packageNames = context.getPackageManager().getPackagesForUid(
+ attributionSource.getUid());
+ if (packageNames != null) {
+ // This is best effort if the caller doesn't pass a package. The security
+ // sandbox is UID, therefore we pick an arbitrary package.
+ return packageNames[0];
+ }
+ // Last resort to handle special UIDs like root, etc.
+ return AppOpsManager.resolvePackageName(attributionSource.getUid(),
+ attributionSource.getPackageName());
+ }
+
+ private static @NonNull AttributionSource resolveAttributionSource(
+ @NonNull Context context, @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource;
}
- return sService;
+ return new AttributionSource(attributionSource.getUid(),
+ resolvePackageName(context, attributionSource),
+ attributionSource.getAttributionTag(),
+ attributionSource.getToken(),
+ attributionSource.getRenouncedPermissions(),
+ attributionSource.getNext());
}
}