/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.pm.permission;

import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;

import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;

import android.Manifest;
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.AppOpsManager.AttributionFlags;
import android.app.IActivityManager;
import android.content.AttributionSource;
import android.content.AttributionSourceState;
import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.IOnPermissionsChangeListener;
import android.permission.IPermissionChecker;
import android.permission.IPermissionManager;
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.permission.PermissionManagerInternal;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

/**
 * Manages all permissions and handles permissions related tasks.
 */
public class PermissionManagerService extends IPermissionManager.Stub {
    private static final String LOG_TAG = PermissionManagerService.class.getSimpleName();

    /** Map of IBinder -> Running AttributionSource */
    private static final ConcurrentHashMap<IBinder, RegisteredAttribution>
            sRunningAttributionSources = new ConcurrentHashMap<>();

    /** Lock to protect internal data access */
    private final Object mLock = new Object();

    /** Internal connection to the package manager */
    private final PackageManagerInternal mPackageManagerInt;

    /** Internal connection to the user manager */
    private final UserManagerInternal mUserManagerInt;

    /** Map of OneTimePermissionUserManagers keyed by userId */
    @GuardedBy("mLock")
    @NonNull
    private final SparseArray<OneTimePermissionUserManager> mOneTimePermissionUserManagers =
            new SparseArray<>();

    /** App ops manager */
    private final AppOpsManager mAppOpsManager;

    private final Context mContext;
    private final PermissionManagerServiceImpl mPermissionManagerServiceImpl;

    @NonNull
    private final AttributionSourceRegistry mAttributionSourceRegistry;

    @GuardedBy("mLock")
    private CheckPermissionDelegate mCheckPermissionDelegate;

    @Nullable
    private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider;

    PermissionManagerService(@NonNull Context context,
            @NonNull ArrayMap<String, FeatureInfo> availableFeatures) {
        // The package info cache is the cache for package and permission information.
        // Disable the package info and package permission caches locally but leave the
        // checkPermission cache active.
        PackageManager.invalidatePackageInfoCache();
        PermissionManager.disablePackageNamePermissionCache();

        mContext = context;
        mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
        mAppOpsManager = context.getSystemService(AppOpsManager.class);

        mAttributionSourceRegistry = new AttributionSourceRegistry(context);

        PermissionManagerServiceInternalImpl localService =
                new PermissionManagerServiceInternalImpl();
        LocalServices.addService(PermissionManagerServiceInternal.class, localService);
        LocalServices.addService(PermissionManagerInternal.class, localService);

        mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
                availableFeatures);
    }

    /**
     * Creates and returns an initialized, internal service for use by other components.
     * <p>
     * The object returned is identical to the one returned by the LocalServices class using:
     * {@code LocalServices.getService(PermissionManagerServiceInternal.class);}
     * <p>
     * NOTE: The external lock is temporary and should be removed. This needs to be a
     * lock created by the permission manager itself.
     */
    @NonNull
    public static PermissionManagerServiceInternal create(@NonNull Context context,
            ArrayMap<String, FeatureInfo> availableFeatures) {
        final PermissionManagerServiceInternal permMgrInt =
                LocalServices.getService(PermissionManagerServiceInternal.class);
        if (permMgrInt != null) {
            return permMgrInt;
        }
        PermissionManagerService permissionService =
                (PermissionManagerService) ServiceManager.getService("permissionmgr");
        if (permissionService == null) {
            permissionService = new PermissionManagerService(context, availableFeatures);
            ServiceManager.addService("permissionmgr", permissionService);
            ServiceManager.addService("permission_checker", new PermissionCheckerService(context));
        }
        return LocalServices.getService(PermissionManagerServiceInternal.class);
    }

    /**
     * TODO: theianchen we want to remove this method in the future.
     * There's a complete copy of this method in PermissionManagerServiceImpl
     *
     * This method should typically only be used when granting or revoking
     * permissions, since the app may immediately restart after this call.
     * <p>
     * If you're doing surgery on app code/data, use {@link PackageFreezer} to
     * guard your work against the app being relaunched.
     */
    public static void killUid(int appId, int userId, String reason) {
        final long identity = Binder.clearCallingIdentity();
        try {
            IActivityManager am = ActivityManager.getService();
            if (am != null) {
                try {
                    am.killUidForPermissionChange(appId, userId, reason);
                } catch (RemoteException e) {
                    /* ignore - same process */
                }
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private int checkPermission(String pkgName, String permName, @UserIdInt int userId) {
        // Not using Objects.requireNonNull() here for compatibility reasons.
        if (pkgName == null || permName == null) {
            return PackageManager.PERMISSION_DENIED;
        }

        final CheckPermissionDelegate checkPermissionDelegate;
        synchronized (mLock) {
            checkPermissionDelegate = mCheckPermissionDelegate;
        }

        if (checkPermissionDelegate == null) {
            return mPermissionManagerServiceImpl.checkPermission(pkgName, permName, userId);
        }
        return checkPermissionDelegate.checkPermission(pkgName, permName, userId,
                mPermissionManagerServiceImpl::checkPermission);
    }

    private int checkUidPermission(int uid, String permName) {
        // Not using Objects.requireNonNull() here for compatibility reasons.
        if (permName == null) {
            return PackageManager.PERMISSION_DENIED;
        }

        final CheckPermissionDelegate checkPermissionDelegate;
        synchronized (mLock) {
            checkPermissionDelegate = mCheckPermissionDelegate;
        }

        if (checkPermissionDelegate == null)  {
            return mPermissionManagerServiceImpl.checkUidPermission(uid, permName);
        }
        return checkPermissionDelegate.checkUidPermission(uid, permName,
                mPermissionManagerServiceImpl::checkUidPermission);
    }

    @Override
    public boolean setAutoRevokeExempted(
            @NonNull String packageName, boolean exempted, int userId) {
        Objects.requireNonNull(packageName);

        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
        final int callingUid = Binder.getCallingUid();

        if (!checkAutoRevokeAccess(pkg, callingUid)) {
            return false;
        }

        return setAutoRevokeExemptedInternal(pkg, exempted, userId);
    }

    private boolean setAutoRevokeExemptedInternal(@NonNull AndroidPackage pkg, boolean exempted,
            @UserIdInt int userId) {
        final int packageUid = UserHandle.getUid(userId, pkg.getUid());
        if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
                packageUid, pkg.getPackageName()) != MODE_ALLOWED) {
            // Allowlist user set - don't override
            return false;
        }

        final long identity = Binder.clearCallingIdentity();
        try {
            mAppOpsManager.setMode(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid,
                    pkg.getPackageName(), exempted ? MODE_IGNORED : MODE_ALLOWED);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return true;
    }

    private boolean checkAutoRevokeAccess(AndroidPackage pkg, int callingUid) {
        if (pkg == null) {
            return false;
        }

        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
                Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS)
                == PackageManager.PERMISSION_GRANTED;
        final boolean isCallerInstallerOnRecord =
                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);

        if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
            throw new SecurityException("Caller must either hold "
                    + Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS
                    + " or be the installer on record");
        }
        return true;
    }

    @Override
    public boolean isAutoRevokeExempted(@NonNull String packageName, int userId) {
        Objects.requireNonNull(packageName);

        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
        final int callingUid = Binder.getCallingUid();
        if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) {
            return false;
        }

        if (!checkAutoRevokeAccess(pkg, callingUid)) {
            return false;
        }

        final int packageUid = UserHandle.getUid(userId, pkg.getUid());

        final long identity = Binder.clearCallingIdentity();
        try {
            return mAppOpsManager.checkOpNoThrow(
                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid, packageName)
                    == MODE_IGNORED;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void startShellPermissionIdentityDelegationInternal(int uid,
            @NonNull String packageName, @Nullable List<String> permissionNames) {
        synchronized (mLock) {
            final CheckPermissionDelegate oldDelegate = mCheckPermissionDelegate;
            if (oldDelegate != null && oldDelegate.getDelegatedUid() != uid) {
                throw new SecurityException(
                        "Shell can delegate permissions only to one UID at a time");
            }
            final ShellDelegate delegate = new ShellDelegate(uid, packageName, permissionNames);
            setCheckPermissionDelegateLocked(delegate);
        }
    }

    private void stopShellPermissionIdentityDelegationInternal() {
        synchronized (mLock) {
            setCheckPermissionDelegateLocked(null);
        }
    }

    @Nullable
    private List<String> getDelegatedShellPermissionsInternal() {
        synchronized (mLock) {
            if (mCheckPermissionDelegate == null) {
                return Collections.EMPTY_LIST;
            }
            return mCheckPermissionDelegate.getDelegatedPermissionNames();
        }
    }

    private void setCheckPermissionDelegateLocked(@Nullable CheckPermissionDelegate delegate) {
        if (delegate != null || mCheckPermissionDelegate != null) {
            PackageManager.invalidatePackageInfoCache();
        }
        mCheckPermissionDelegate = delegate;
    }

    @NonNull
    private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) {
        OneTimePermissionUserManager oneTimePermissionUserManager;
        synchronized (mLock) {
            oneTimePermissionUserManager = mOneTimePermissionUserManagers.get(userId);
            if (oneTimePermissionUserManager != null) {
                return oneTimePermissionUserManager;
            }
        }
        // We cannot create a new instance of OneTimePermissionUserManager while holding our own
        // lock, which may lead to a deadlock with the package manager lock. So we do it in a
        // retry-like way, and just discard the newly created instance if someone else managed to be
        // a little bit faster than us when we dropped our own lock.
        final OneTimePermissionUserManager newOneTimePermissionUserManager =
                new OneTimePermissionUserManager(mContext.createContextAsUser(UserHandle.of(userId),
                        /*flags*/ 0));
        synchronized (mLock) {
            oneTimePermissionUserManager = mOneTimePermissionUserManagers.get(userId);
            if (oneTimePermissionUserManager != null) {
                return oneTimePermissionUserManager;
            }
            oneTimePermissionUserManager = newOneTimePermissionUserManager;
            mOneTimePermissionUserManagers.put(userId, oneTimePermissionUserManager);
        }
        oneTimePermissionUserManager.registerUninstallListener();
        return oneTimePermissionUserManager;
    }

    @Override
    public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
            long timeoutMillis, long revokeAfterKilledDelayMillis) {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
                        + " to register permissions as one time.");
        Objects.requireNonNull(packageName);

        final long token = Binder.clearCallingIdentity();
        try {
            getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
                    timeoutMillis, revokeAfterKilledDelayMillis);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) {
        mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
                        + " to remove permissions as one time.");
        Objects.requireNonNull(packageName);

        final long token = Binder.clearCallingIdentity();
        try {
            getOneTimePermissionUserManager(userId).stopPackageOneTimeSession(packageName);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void registerAttributionSource(@NonNull AttributionSourceState source) {
        mAttributionSourceRegistry
                .registerAttributionSource(new AttributionSource(source));
    }

    @Override
    public boolean isRegisteredAttributionSource(@NonNull AttributionSourceState source) {
        return mAttributionSourceRegistry
                .isRegisteredAttributionSource(new AttributionSource(source));
    }

    @Override
    public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
        return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
    }

    @Override
    public List<String> getAutoRevokeExemptionGrantedPackages(int userId) {
        return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISALLOWED, userId);
    }

    @NonNull
    private List<String> getPackagesWithAutoRevokePolicy(int autoRevokePolicy, int userId) {
        mContext.enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
                "Must hold " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);

        List<String> result = new ArrayList<>();
        mPackageManagerInt.forEachInstalledPackage(pkg -> {
            if (pkg.getAutoRevokePermissions() == autoRevokePolicy) {
                result.add(pkg.getPackageName());
            }
        }, userId);
        return result;
    }

    /* Start of delegate methods to PermissionManagerServiceInterface */

    @Override
    public ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
        return new ParceledListSlice<>(mPermissionManagerServiceImpl.getAllPermissionGroups(flags));
    }

    @Override
    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
        return mPermissionManagerServiceImpl.getPermissionGroupInfo(groupName, flags);
    }

    @Override
    public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) {
        return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags);
    }

    @Override
    public ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) {
        List<PermissionInfo> permissionInfo =
                mPermissionManagerServiceImpl.queryPermissionsByGroup(groupName, flags);
        if (permissionInfo == null) {
            return null;
        }

        return new ParceledListSlice<>(permissionInfo);
    }

    @Override
    public boolean addPermission(PermissionInfo permissionInfo, boolean async) {
        return mPermissionManagerServiceImpl.addPermission(permissionInfo, async);
    }

    @Override
    public void removePermission(String permissionName) {
        mPermissionManagerServiceImpl.removePermission(permissionName);
    }

    @Override
    public int getPermissionFlags(String packageName, String permissionName, int userId) {
        return mPermissionManagerServiceImpl
                .getPermissionFlags(packageName, permissionName, userId);
    }

    @Override
    public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
        mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
                flagValues, checkAdjustPolicyFlagPermission, userId);
    }

    @Override
    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
        mPermissionManagerServiceImpl.updatePermissionFlagsForAllApps(flagMask, flagValues, userId);
    }

    @Override
    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
        mPermissionManagerServiceImpl.addOnPermissionsChangeListener(listener);
    }

    @Override
    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
        mPermissionManagerServiceImpl.removeOnPermissionsChangeListener(listener);
    }

    @Override
    public List<String> getAllowlistedRestrictedPermissions(String packageName,
            int flags, int userId) {
        return mPermissionManagerServiceImpl.getAllowlistedRestrictedPermissions(packageName,
                flags, userId);
    }

    @Override
    public boolean addAllowlistedRestrictedPermission(String packageName, String permissionName,
            int flags, int userId) {
        return mPermissionManagerServiceImpl.addAllowlistedRestrictedPermission(packageName,
                permissionName, flags, userId);
    }

    @Override
    public boolean removeAllowlistedRestrictedPermission(String packageName, String permissionName,
            int flags, int userId) {
        return mPermissionManagerServiceImpl.removeAllowlistedRestrictedPermission(packageName,
                permissionName, flags, userId);
    }

    @Override
    public void grantRuntimePermission(String packageName, String permissionName, int userId) {
        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
    }

    @Override
    public void revokeRuntimePermission(String packageName, String permissionName, int userId,
            String reason) {
        mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, userId,
                reason);
    }

    @Override
    public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) {
        mPermissionManagerServiceImpl.revokePostNotificationPermissionWithoutKillForTest(
                packageName, userId);
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
            int userId) {
        return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
                permissionName, userId);
    }

    @Override
    public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
            int userId) {
        return mPermissionManagerServiceImpl
                .isPermissionRevokedByPolicy(packageName, permissionName, userId);
    }

    @Override
    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
        return mPermissionManagerServiceImpl.getSplitPermissions();
    }

    /* End of delegate methods to PermissionManagerServiceInterface */

    private class PermissionManagerServiceInternalImpl implements PermissionManagerServiceInternal {
        @Override
        public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
                @UserIdInt int userId) {
            return PermissionManagerService.this.checkPermission(packageName, permissionName,
                    userId);
        }

        @Override
        public int checkUidPermission(int uid, @NonNull String permissionName) {
            return PermissionManagerService.this.checkUidPermission(uid, permissionName);
        }

        @Override
        public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
                @Nullable List<String> permissionNames) {
            Objects.requireNonNull(packageName, "packageName");
            startShellPermissionIdentityDelegationInternal(uid, packageName, permissionNames);
        }

        @Override
        public void stopShellPermissionIdentityDelegation() {
            stopShellPermissionIdentityDelegationInternal();
        }

        @Override
        @NonNull
        public List<String> getDelegatedShellPermissions() {
            return getDelegatedShellPermissionsInternal();
        }

        @Override
        public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) {
            mHotwordDetectionServiceProvider = provider;
        }

        @Override
        public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() {
            return mHotwordDetectionServiceProvider;
        }

        /* Start of delegate methods to PermissionManagerServiceInterface */

        @NonNull
        @Override
        public int[] getGidsForUid(int uid) {
            return mPermissionManagerServiceImpl.getGidsForUid(uid);
        }

        @NonNull
        @Override
        public Map<String, Set<String>> getAllAppOpPermissionPackages() {
            return mPermissionManagerServiceImpl.getAllAppOpPermissionPackages();
        }

        @Override
        public void onUserCreated(@UserIdInt int userId) {
            mPermissionManagerServiceImpl.onUserCreated(userId);
        }

        @NonNull
        @Override
        public List<LegacyPermission> getLegacyPermissions() {
            return mPermissionManagerServiceImpl.getLegacyPermissions();
        }

        @NonNull
        @Override
        public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
            return mPermissionManagerServiceImpl.getLegacyPermissionState(appId);
        }

        @Nullable
        @Override
        public byte[] backupRuntimePermissions(@UserIdInt int userId) {
            return mPermissionManagerServiceImpl.backupRuntimePermissions(userId);
        }

        @Override
        public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) {
            mPermissionManagerServiceImpl.restoreRuntimePermissions(backup, userId);
        }

        @Override
        public void restoreDelayedRuntimePermissions(@NonNull String packageName,
                @UserIdInt int userId) {
            mPermissionManagerServiceImpl.restoreDelayedRuntimePermissions(packageName, userId);
        }

        @Override
        public void readLegacyPermissionsTEMP(
                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
            mPermissionManagerServiceImpl.readLegacyPermissionsTEMP(legacyPermissionSettings);
        }

        @Override
        public void writeLegacyPermissionsTEMP(
                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
            mPermissionManagerServiceImpl.writeLegacyPermissionsTEMP(legacyPermissionSettings);
        }

        @Override
        public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
                @Nullable AndroidPackage oldPkg) {
            mPermissionManagerServiceImpl.onPackageAdded(pkg, isInstantApp, oldPkg);
        }

        @Override
        public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
                @NonNull PackageInstalledParams params, @UserIdInt int rawUserId) {
            Objects.requireNonNull(pkg, "pkg");
            Objects.requireNonNull(params, "params");
            Preconditions.checkArgument(rawUserId >= UserHandle.USER_SYSTEM
                    || rawUserId == UserHandle.USER_ALL, "userId");

            mPermissionManagerServiceImpl.onPackageInstalled(pkg, previousAppId, params, rawUserId);
            final int[] userIds = rawUserId == UserHandle.USER_ALL ? getAllUserIds()
                    : new int[] { rawUserId };
            for (final int userId : userIds) {
                final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode();
                if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED
                        || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) {
                    setAutoRevokeExemptedInternal(pkg,
                            autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId);
                }
            }
        }

        @Override
        public void onPackageRemoved(@NonNull AndroidPackage pkg) {
            mPermissionManagerServiceImpl.onPackageRemoved(pkg);
        }

        @Override
        public void onPackageUninstalled(@NonNull String packageName, int appId,
                @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
                @UserIdInt int userId) {
            mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId,
                    pkg, sharedUserPkgs, userId);
        }

        @Override
        public void addOnRuntimePermissionStateChangedListener(
                OnRuntimePermissionStateChangedListener listener) {
            mPermissionManagerServiceImpl.addOnRuntimePermissionStateChangedListener(listener);
        }

        @Override
        public void removeOnRuntimePermissionStateChangedListener(
                OnRuntimePermissionStateChangedListener listener) {
            mPermissionManagerServiceImpl.removeOnRuntimePermissionStateChangedListener(listener);
        }

        @Override
        public void onSystemReady() {
            mPermissionManagerServiceImpl.onSystemReady();
        }

        @Override
        public boolean isPermissionsReviewRequired(@NonNull String packageName,
                @UserIdInt int userId) {
            return mPermissionManagerServiceImpl.isPermissionsReviewRequired(packageName, userId);
        }

        @Override
        public void readLegacyPermissionStateTEMP() {
            mPermissionManagerServiceImpl.readLegacyPermissionStateTEMP();
        }
        @Override
        public void writeLegacyPermissionStateTEMP() {
            mPermissionManagerServiceImpl.writeLegacyPermissionStateTEMP();
        }
        @Override
        public void onUserRemoved(@UserIdInt int userId) {
            mPermissionManagerServiceImpl.onUserRemoved(userId);
        }
        @NonNull
        @Override
        public Set<String> getGrantedPermissions(@NonNull String packageName,
                @UserIdInt int userId) {
            return mPermissionManagerServiceImpl.getGrantedPermissions(packageName, userId);
        }
        @NonNull
        @Override
        public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
            return mPermissionManagerServiceImpl.getPermissionGids(permissionName, userId);
        }
        @NonNull
        @Override
        public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
            return mPermissionManagerServiceImpl.getAppOpPermissionPackages(permissionName);
        }
        @Override
        public void onStorageVolumeMounted(@Nullable String volumeUuid,
                boolean fingerprintChanged) {
            mPermissionManagerServiceImpl.onStorageVolumeMounted(volumeUuid, fingerprintChanged);
        }
        @Override
        public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
            mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId);
        }

        @Override
        public Permission getPermissionTEMP(String permName) {
            return mPermissionManagerServiceImpl.getPermissionTEMP(permName);
        }

        @NonNull
        @Override
        public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
                @PermissionInfo.Protection int protection) {
            return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection);
        }

        @NonNull
        @Override
        public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
                @PermissionInfo.ProtectionFlags int protectionFlags) {
            return mPermissionManagerServiceImpl
                    .getAllPermissionsWithProtectionFlags(protectionFlags);
        }

        /* End of delegate methods to PermissionManagerServiceInterface */
    }

    /**
     * Returns all relevant user ids.  This list include the current set of created user ids as well
     * as pre-created user ids.
     * @return user ids for created users and pre-created users
     */
    private int[] getAllUserIds() {
        return UserManagerService.getInstance().getUserIdsIncludingPreCreated();
    }

    /**
     * Interface to intercept permission checks and optionally pass through to the original
     * implementation.
     */
    private interface CheckPermissionDelegate {
        /**
         * Get the UID whose permission checks is being delegated.
         *
         * @return the UID
         */
        int getDelegatedUid();

        /**
         * Check whether the given package has been granted the specified permission.
         *
         * @param packageName the name of the package to be checked
         * @param permissionName the name of the permission to be checked
         * @param userId the user ID
         * @param superImpl the original implementation that can be delegated to
         * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
         * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
         *
         * @see android.content.pm.PackageManager#checkPermission(String, String)
         */
        int checkPermission(@NonNull String packageName, @NonNull String permissionName,
                @UserIdInt int userId,
                @NonNull TriFunction<String, String, Integer, Integer> superImpl);

        /**
         * Check whether the given UID has been granted the specified permission.
         *
         * @param uid the UID to be checked
         * @param permissionName the name of the permission to be checked
         * @param superImpl the original implementation that can be delegated to
         * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
         * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
         */
        int checkUidPermission(int uid, @NonNull String permissionName,
                BiFunction<Integer, String, Integer> superImpl);

        /**
         * @return list of delegated permissions
         */
        List<String> getDelegatedPermissionNames();
    }

    private class ShellDelegate implements CheckPermissionDelegate {
        private final int mDelegatedUid;
        @NonNull
        private final String mDelegatedPackageName;
        @Nullable
        private final List<String> mDelegatedPermissionNames;

        public ShellDelegate(int delegatedUid, @NonNull String delegatedPackageName,
                @Nullable List<String> delegatedPermissionNames) {
            mDelegatedUid = delegatedUid;
            mDelegatedPackageName = delegatedPackageName;
            mDelegatedPermissionNames = delegatedPermissionNames;
        }

        @Override
        public int getDelegatedUid() {
            return mDelegatedUid;
        }

        @Override
        public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
                int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) {
            if (mDelegatedPackageName.equals(packageName)
                    && isDelegatedPermission(permissionName)) {
                final long identity = Binder.clearCallingIdentity();
                try {
                    return superImpl.apply("com.android.shell", permissionName, userId);
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
            }
            return superImpl.apply(packageName, permissionName, userId);
        }

        @Override
        public int checkUidPermission(int uid, @NonNull String permissionName,
                @NonNull BiFunction<Integer, String, Integer> superImpl) {
            if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) {
                final long identity = Binder.clearCallingIdentity();
                try {
                    return superImpl.apply(Process.SHELL_UID, permissionName);
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
            }
            return superImpl.apply(uid, permissionName);
        }

        @Override
        public List<String> getDelegatedPermissionNames() {
            return mDelegatedPermissionNames == null
                    ? null
                    : new ArrayList<>(mDelegatedPermissionNames);
        }

        private boolean isDelegatedPermission(@NonNull String permissionName) {
            // null permissions means all permissions are targeted
            return mDelegatedPermissionNames == null
                    || mDelegatedPermissionNames.contains(permissionName);
        }
    }

    private static final class AttributionSourceRegistry {
        private final Object mLock = new Object();

        private final Context mContext;

        AttributionSourceRegistry(@NonNull Context context) {
            mContext = context;
        }

        private final WeakHashMap<IBinder, AttributionSource> mAttributions = new WeakHashMap<>();

        public void registerAttributionSource(@NonNull AttributionSource source) {
            //   Here we keep track of attribution sources that were created by an app
            // from an attribution chain that called into the app and the apps's
            // own attribution source. An app can register an attribution chain up
            // to itself inclusive if and only if it is adding a node for itself which
            // optionally points to an attribution chain that was created by each
            // preceding app recursively up to the beginning of the chain.
            //   The only special case is when the first app in the attribution chain
            // creates a source that points to another app (not a chain of apps). We
            // allow this even if the source the app points to is not registered since
            // in app ops we allow every app to blame every other app (untrusted if not
            // holding a special permission).
            //  This technique ensures that a bad actor in the middle of the attribution
            // chain can neither prepend nor append an invalid attribution sequence, i.e.
            // a sequence that is not constructed by trusted sources up to the that bad
            // actor's app.
            //   Note that passing your attribution source to another app means you allow
            // it to blame private data access on your app. This can be mediated by the OS
            // in, which case security is already enforced; by other app's code running in
            // your process calling into the other app, in which case it can already access
            // the private data in your process; or by you explicitly calling to another
            // app passing the source, in which case you must trust the other side;

            final int callingUid = Binder.getCallingUid();
            if (source.getUid() != callingUid && mContext.checkPermission(
                    Manifest.permission.UPDATE_APP_OPS_STATS, /*pid*/ -1, callingUid)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException("Cannot register attribution source for uid:"
                        + source.getUid() + " from uid:" + callingUid);
            }

            final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                    PackageManagerInternal.class);

            // TODO(b/234653108): Clean up this UID/package & cross-user check.
            // If calling from the system process, allow registering attribution for package from
            // any user
            int userId = UserHandle.getUserId((callingUid == Process.SYSTEM_UID ? source.getUid()
                    : callingUid));
            if (packageManagerInternal.getPackageUid(source.getPackageName(), 0, userId)
                    != source.getUid()) {
                throw new SecurityException("Cannot register attribution source for package:"
                        + source.getPackageName() + " from uid:" + callingUid);
            }

            final AttributionSource next = source.getNext();
            if (next != null && next.getNext() != null
                    && !isRegisteredAttributionSource(next)) {
                throw new SecurityException("Cannot register forged attribution source:"
                        + source);
            }

            synchronized (mLock) {
                mAttributions.put(source.getToken(), source);
            }
        }

        public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
            synchronized (mLock) {
                final AttributionSource cachedSource = mAttributions.get(source.getToken());
                if (cachedSource != null) {
                    return cachedSource.equals(source);
                }
                return false;
            }
        }
    }

    /**
     * TODO: We need to consolidate these APIs either on PermissionManager or an extension
     * object or a separate PermissionChecker service in context. The impartant part is to
     * keep a single impl that is exposed to Java and native. We are not sure about the
     * API shape so let is soak a bit.
     */
    private static final class PermissionCheckerService extends IPermissionChecker.Stub {
        // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
        private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
                = new ConcurrentHashMap<>();

        private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);

        private final @NonNull Context mContext;
        private final @NonNull AppOpsManager mAppOpsManager;
        private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;

        PermissionCheckerService(@NonNull Context context) {
            mContext = context;
            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
            mPermissionManagerServiceInternal =
                    LocalServices.getService(PermissionManagerServiceInternal.class);
        }

        @Override
        @PermissionCheckerManager.PermissionResult
        public int checkPermission(@NonNull String permission,
                @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
                int attributedOp) {
            Objects.requireNonNull(permission);
            Objects.requireNonNull(attributionSourceState);
            final AttributionSource attributionSource = new AttributionSource(
                    attributionSourceState);
            final int result = checkPermission(mContext, mPermissionManagerServiceInternal,
                    permission, attributionSource, message, forDataDelivery, startDataDelivery,
                    fromDatasource, attributedOp);
            // Finish any started op if some step in the attribution chain failed.
            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
                if (attributedOp == AppOpsManager.OP_NONE) {
                    finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                            attributionSource.asState(), fromDatasource);
                } else {
                    finishDataDelivery(attributedOp, attributionSource.asState(), fromDatasource);
                }
            }
            return result;
        }

        @Override
        public void finishDataDelivery(int op,
                @NonNull AttributionSourceState attributionSourceState, boolean fromDataSource) {
            finishDataDelivery(mContext, op, attributionSourceState,
                    fromDataSource);
        }

        private static void finishDataDelivery(Context context, int op,
                @NonNull AttributionSourceState attributionSourceState, boolean fromDatasource) {
            Objects.requireNonNull(attributionSourceState);
            AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);

            if (op == AppOpsManager.OP_NONE) {
                return;
            }

            AttributionSource current = new AttributionSource(attributionSourceState);
            AttributionSource next = null;

            while (true) {
                final boolean skipCurrentFinish = (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.asState() == attributionSourceState)
                        && next != null && !current.isTrusted(context)) {
                    return;
                }

                // 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.asState() == attributionSourceState && next != null
                        && next.getNext() == null);
                final boolean selfAccess = singleReceiverFromDatasource || next == null;

                final AttributionSource accessorSource = (!singleReceiverFromDatasource)
                        ? current : next;

                if (selfAccess) {
                    final String resolvedPackageName = resolvePackageName(context, accessorSource);
                    if (resolvedPackageName == null) {
                        return;
                    }
                    appOpsManager.finishOp(attributionSourceState.token, op,
                            accessorSource.getUid(), resolvedPackageName,
                            accessorSource.getAttributionTag());
                } else {
                    final AttributionSource resolvedAttributionSource =
                            resolveAttributionSource(context, accessorSource);
                    if (resolvedAttributionSource.getPackageName() == null) {
                        return;
                    }
                    appOpsManager.finishProxyOp(attributionSourceState.token,
                            AppOpsManager.opToPublicName(op), resolvedAttributionSource,
                            skipCurrentFinish);
                }
                RegisteredAttribution registered =
                        sRunningAttributionSources.remove(current.getToken());
                if (registered != null) {
                    registered.unregister();
                }

                if (next == null || next.getNext() == null) {
                    if (next != null) {
                        registered = sRunningAttributionSources.remove(next.getToken());
                        if (registered != null) {
                            registered.unregister();
                        }
                    }
                    return;
                }
                current = next;
            }
        }

        @Override
        @PermissionCheckerManager.PermissionResult
        public int checkOp(int op, AttributionSourceState attributionSource,
                String message, boolean forDataDelivery, boolean startDataDelivery) {
            int result = checkOp(mContext, op, mPermissionManagerServiceInternal,
                    new AttributionSource(attributionSource), message, forDataDelivery,
                    startDataDelivery);
            if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) {
                // Finish any started op if some step in the attribution chain failed.
                finishDataDelivery(op, attributionSource, /*fromDatasource*/ false);
            }
            return result;
        }

        @PermissionCheckerManager.PermissionResult
        private static int checkPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, @NonNull AttributionSource attributionSource,
                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
                boolean fromDatasource, int attributedOp) {
            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 PermissionChecker.PERMISSION_HARD_DENIED;
                }
            }

            if (permissionInfo.isAppOp()) {
                return checkAppOpPermission(context, permissionManagerServiceInt, permission,
                        attributionSource, message, forDataDelivery, fromDatasource);
            }
            if (permissionInfo.isRuntime()) {
                return checkRuntimePermission(context, permissionManagerServiceInt, permission,
                        attributionSource, message, forDataDelivery, startDataDelivery,
                        fromDatasource, attributedOp);
            }

            if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt,
                    permission, attributionSource.getUid(),
                    attributionSource.getRenouncedPermissions())) {
                return PermissionChecker.PERMISSION_HARD_DENIED;
            }

            if (attributionSource.getNext() != null) {
                return checkPermission(context, permissionManagerServiceInt, permission,
                        attributionSource.getNext(), message, forDataDelivery, startDataDelivery,
                        /*fromDatasource*/ false, attributedOp);
            }

            return PermissionChecker.PERMISSION_GRANTED;
        }

        @PermissionCheckerManager.PermissionResult
        private static int checkAppOpPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @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 PermissionChecker.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.equals(attributionSource))
                        && next != null && !current.isTrusted(context)) {
                    return PermissionChecker.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.equals(attributionSource) && next != null
                        && next.getNext() == null);
                final boolean selfAccess = singleReceiverFromDatasource || next == null;

                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
                        current, message, forDataDelivery, /*startDataDelivery*/ false,
                        skipCurrentChecks, selfAccess, singleReceiverFromDatasource,
                        AppOpsManager.OP_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                        AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                        AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);

                switch (opMode) {
                    case AppOpsManager.MODE_IGNORED:
                    case AppOpsManager.MODE_ERRORED: {
                        return PermissionChecker.PERMISSION_HARD_DENIED;
                    }
                    case AppOpsManager.MODE_DEFAULT: {
                        if (!skipCurrentChecks && !checkPermission(context,
                                permissionManagerServiceInt, permission, attributionSource.getUid(),
                                attributionSource.getRenouncedPermissions())) {
                            return PermissionChecker.PERMISSION_HARD_DENIED;
                        }
                        if (next != null && !checkPermission(context, permissionManagerServiceInt,
                                permission, next.getUid(), next.getRenouncedPermissions())) {
                            return PermissionChecker.PERMISSION_HARD_DENIED;
                        }
                    }
                }

                if (next == null || next.getNext() == null) {
                    return PermissionChecker.PERMISSION_GRANTED;
                }

                current = next;
            }
        }

        private static int checkRuntimePermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, @NonNull AttributionSource attributionSource,
                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
                boolean fromDatasource, int attributedOp) {
            // Now let's check the identity chain...
            final int op = AppOpsManager.permissionToOpCode(permission);
            final int attributionChainId =
                    getAttributionChainId(startDataDelivery, attributionSource);
            final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
            AttributionSource current = attributionSource;
            AttributionSource next = null;
            // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
            // every attributionSource in the chain is registered with the system.
            final boolean isChainStartTrusted = !hasChain || checkPermission(context,
                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
                    current.getRenouncedPermissions());

            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.equals(attributionSource))
                        && next != null && !current.isTrusted(context)) {
                    return PermissionChecker.PERMISSION_HARD_DENIED;
                }

                // If we already checked the permission for this one, skip the work
                if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
                        permission, current.getUid(), current.getRenouncedPermissions())) {
                    return PermissionChecker.PERMISSION_HARD_DENIED;
                }

                if (next != null && !checkPermission(context, permissionManagerServiceInt,
                        permission, next.getUid(), next.getRenouncedPermissions())) {
                    return PermissionChecker.PERMISSION_HARD_DENIED;
                }

                if (op < 0) {
                    // Bg location is one-off runtime modifier permission and has no app op
                    if (sPlatformPermissions.containsKey(permission)
                            && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
                        Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
                                + " with no app op defined!");
                    }
                    if (next == null) {
                        return PermissionChecker.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.equals(attributionSource)
                        && next != null && next.getNext() == null);
                final boolean selfAccess = singleReceiverFromDatasource || next == null;
                final boolean isLinkTrusted = isChainStartTrusted
                        && (current.isTrusted(context) || current.equals(attributionSource))
                        && (next == null || next.isTrusted(context));

                final int proxyAttributionFlags = (!skipCurrentChecks && hasChain)
                        ? resolveProxyAttributionFlags(attributionSource, current, fromDatasource,
                                startDataDelivery, selfAccess, isLinkTrusted)
                        : ATTRIBUTION_FLAGS_NONE;
                final int proxiedAttributionFlags = hasChain ? resolveProxiedAttributionFlags(
                        attributionSource, next, fromDatasource, startDataDelivery, selfAccess,
                        isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;

                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
                        current, message, forDataDelivery, startDataDelivery, skipCurrentChecks,
                        selfAccess, singleReceiverFromDatasource, attributedOp,
                        proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);

                switch (opMode) {
                    case AppOpsManager.MODE_ERRORED: {
                        return PermissionChecker.PERMISSION_HARD_DENIED;
                    }
                    case AppOpsManager.MODE_IGNORED: {
                        return PermissionChecker.PERMISSION_SOFT_DENIED;
                    }
                }

                if (startDataDelivery) {
                    RegisteredAttribution registered = new RegisteredAttribution(context, op,
                            current, fromDatasource);
                    sRunningAttributionSources.put(current.getToken(), registered);
                }

                if (next == null || next.getNext() == null) {
                    return PermissionChecker.PERMISSION_GRANTED;
                }

                current = next;
            }
        }

        private static boolean checkPermission(@NonNull Context context,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) {
            boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
                    uid) == PackageManager.PERMISSION_GRANTED;

            // Override certain permissions checks for the HotwordDetectionService. This service is
            // an isolated service, which ordinarily cannot hold permissions.
            // There's probably a cleaner, more generalizable way to do this. For now, this is
            // the only use case for this, so simply override here.
            if (!permissionGranted
                    && Process.isIsolated(uid) // simple check which fails-fast for the common case
                    && (permission.equals(RECORD_AUDIO) || permission.equals(CAPTURE_AUDIO_HOTWORD)
                    || permission.equals(CAPTURE_AUDIO_OUTPUT))) {
                HotwordDetectionServiceProvider hotwordServiceProvider =
                        permissionManagerServiceInt.getHotwordDetectionServiceProvider();
                permissionGranted = hotwordServiceProvider != null
                        && uid == hotwordServiceProvider.getUid();
            }

            if (permissionGranted && renouncedPermissions.contains(permission)
                    && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
                    /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            return permissionGranted;
        }

        private static @AttributionFlags int resolveProxyAttributionFlags(
                @NonNull AttributionSource attributionChain,
                @NonNull AttributionSource current, boolean fromDatasource,
                boolean startDataDelivery, boolean selfAccess, boolean isTrusted) {
            return resolveAttributionFlags(attributionChain, current, fromDatasource,
                    startDataDelivery, selfAccess, isTrusted, /*flagsForProxy*/ true);
        }

        private static @AttributionFlags int resolveProxiedAttributionFlags(
                @NonNull AttributionSource attributionChain,
                @NonNull AttributionSource current, boolean fromDatasource,
                boolean startDataDelivery, boolean selfAccess, boolean isTrusted) {
            return resolveAttributionFlags(attributionChain, current, fromDatasource,
                    startDataDelivery, selfAccess, isTrusted, /*flagsForProxy*/ false);
        }

        private static @AttributionFlags int resolveAttributionFlags(
                @NonNull AttributionSource attributionChain,
                @NonNull AttributionSource current, boolean fromDatasource,
                boolean startDataDelivery, boolean selfAccess, boolean isTrusted,
                boolean flagsForProxy) {
            if (current == null || !startDataDelivery) {
                return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
            }
            int trustedFlag = isTrusted
                    ? AppOpsManager.ATTRIBUTION_FLAG_TRUSTED : AppOpsManager.ATTRIBUTION_FLAGS_NONE;
            if (flagsForProxy) {
                if (selfAccess) {
                    return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
                } else if (!fromDatasource && current.equals(attributionChain)) {
                    return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
                }
            } else {
                if (selfAccess) {
                    return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
                } else if (fromDatasource && current.equals(attributionChain.getNext())) {
                    return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
                } else if (current.getNext() == null) {
                    return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
                }
            }
            if (fromDatasource && current.equals(attributionChain)) {
                return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
            }
            return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY;
        }

        private static int checkOp(@NonNull Context context, @NonNull int op,
                @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
                @NonNull AttributionSource attributionSource, @Nullable String message,
                boolean forDataDelivery, boolean startDataDelivery) {
            if (op < 0 || attributionSource.getPackageName() == null) {
                return PermissionChecker.PERMISSION_HARD_DENIED;
            }

            final int attributionChainId =
                    getAttributionChainId(startDataDelivery, attributionSource);
            final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;

            AttributionSource current = attributionSource;
            AttributionSource next = null;

            // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
            // every attributionSource in the chain is registered with the system.
            final boolean isChainStartTrusted = !hasChain || checkPermission(context,
                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
                    current.getRenouncedPermissions());

            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 PermissionChecker.PERMISSION_HARD_DENIED;
                }

                // The access is for oneself if this is the single attribution source in the chain.
                final boolean selfAccess = (next == null);
                final boolean isLinkTrusted = isChainStartTrusted
                        && (current.isTrusted(context) || current.equals(attributionSource))
                        && (next == null || next.isTrusted(context));

                final int proxyAttributionFlags = (!skipCurrentChecks && hasChain)
                        ? resolveProxyAttributionFlags(attributionSource, current,
                                /*fromDatasource*/ false, startDataDelivery, selfAccess,
                        isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
                final int proxiedAttributionFlags = hasChain ? resolveProxiedAttributionFlags(
                        attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
                        selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;

                final int opMode = performOpTransaction(context, current.getToken(), op, current,
                        message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
                        /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
                        proxiedAttributionFlags, attributionChainId);

                switch (opMode) {
                    case AppOpsManager.MODE_ERRORED: {
                        return PermissionChecker.PERMISSION_HARD_DENIED;
                    }
                    case AppOpsManager.MODE_IGNORED: {
                        return PermissionChecker.PERMISSION_SOFT_DENIED;
                    }
                }

                if (next == null || next.getNext() == null) {
                    return PermissionChecker.PERMISSION_GRANTED;
                }

                current = next;
            }
        }

        @SuppressWarnings("ConstantConditions")
        private static int performOpTransaction(@NonNull Context context,
                @NonNull IBinder chainStartToken, int op,
                @NonNull AttributionSource attributionSource, @Nullable String message,
                boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
                boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
                @AttributionFlags int proxyAttributionFlags,
                @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
            // 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 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.
                int startedOp = op;
                int checkedOpResult = MODE_ALLOWED;
                int startedOpResult;

                // If the datasource wants to attribute to another app op we need to
                // make sure the op for the permission and the attributed ops allow
                // the operation. We return the less permissive of the two and check
                // the permission op while start the attributed op.
                if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
                            resolvedAttributionSource.getUid(), resolvedAttributionSource
                                    .getPackageName());
                    if (checkedOpResult == MODE_ERRORED) {
                        return checkedOpResult;
                    }
                    startedOp = attributedOp;
                }
                if (selfAccess) {
                    try {
                        startedOpResult = appOpsManager.startOpNoThrow(
                                chainStartToken, startedOp,
                                resolvedAttributionSource.getUid(),
                                resolvedAttributionSource.getPackageName(),
                                /*startIfModeDefault*/ false,
                                resolvedAttributionSource.getAttributionTag(),
                                message, proxyAttributionFlags, attributionChainId);
                    } 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);
                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
                                attributedOp, attributionSource, message, skipProxyOperation,
                                proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                    }
                } else {
                    try {
                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
                                startedOp, resolvedAttributionSource, message, skipProxyOperation,
                                proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                    } catch (SecurityException e) {
                        //TODO 195339480: remove
                        String msg = "Security exception for op " + startedOp + " with source "
                                + attributionSource.getUid() + ":"
                                + attributionSource.getPackageName() + ", "
                                + attributionSource.getNextUid() + ":"
                                + attributionSource.getNextPackageName();
                        if (attributionSource.getNext() != null) {
                            AttributionSource next = attributionSource.getNext();
                            msg = msg + ", " + next.getNextPackageName() + ":" + next.getNextUid();
                        }
                        throw new SecurityException(msg + ":" + e.getMessage());
                    }
                }
                return Math.max(checkedOpResult, startedOpResult);
            } else {
                final AttributionSource resolvedAttributionSource = resolveAttributionSource(
                        context, accessorSource);
                if (resolvedAttributionSource.getPackageName() == null) {
                    return AppOpsManager.MODE_ERRORED;
                }
                int notedOp = op;
                int checkedOpResult = MODE_ALLOWED;
                int notedOpResult;

                // If the datasource wants to attribute to another app op we need to
                // make sure the op for the permission and the attributed ops allow
                // the operation. We return the less permissive of the two and check
                // the permission op while start the attributed op.
                if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
                            resolvedAttributionSource.getUid(), resolvedAttributionSource
                                    .getPackageName());
                    if (checkedOpResult == MODE_ERRORED) {
                        return checkedOpResult;
                    }
                    notedOp = attributedOp;
                }
                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 {
                        notedOpResult = appOpsManager.noteOpNoThrow(notedOp,
                                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);
                        notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp, attributionSource,
                                message, skipProxyOperation);
                    }
                } else {
                    try {
                        notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp,
                                resolvedAttributionSource, message, skipProxyOperation);
                    } catch (SecurityException e) {
                        //TODO 195339480: remove
                        String msg = "Security exception for op " + notedOp + " with source "
                                + attributionSource.getUid() + ":"
                                + attributionSource.getPackageName() + ", "
                                + attributionSource.getNextUid() + ":"
                                + attributionSource.getNextPackageName();
                        if (attributionSource.getNext() != null) {
                            AttributionSource next = attributionSource.getNext();
                            msg = msg + ", " + next.getNextPackageName() + ":" + next.getNextUid();
                        }
                        throw new SecurityException(msg + ":" + e.getMessage());
                    }
                }
                return Math.max(checkedOpResult, notedOpResult);
            }
        }

        private static int getAttributionChainId(boolean startDataDelivery,
                AttributionSource source) {
            if (source == null || source.getNext() == null || !startDataDelivery) {
                return AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
            }
            int attributionChainId = sAttributionChainIds.incrementAndGet();

            // handle overflow
            if (attributionChainId < 0) {
                attributionChainId = 0;
                sAttributionChainIds.set(0);
            }
            return attributionChainId;
        }

        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 attributionSource.withPackageName(resolvePackageName(context,
                    attributionSource));
        }
    }

    private static final class RegisteredAttribution {
        private final DeathRecipient mDeathRecipient;
        private final IBinder mToken;
        private final AtomicBoolean mFinished;

        RegisteredAttribution(Context context, int op, AttributionSource source,
                boolean fromDatasource) {
            mFinished = new AtomicBoolean(false);
            mDeathRecipient = () -> {
                if (unregister()) {
                    PermissionCheckerService
                            .finishDataDelivery(context, op, source.asState(), fromDatasource);
                }
            };
            mToken = source.getToken();
            if (mToken != null) {
                try {
                    mToken.linkToDeath(mDeathRecipient, 0);
                } catch (RemoteException e) {
                    mDeathRecipient.binderDied();
                }
            }
        }

        public boolean unregister() {
            if (mFinished.compareAndSet(false, true)) {
                try {
                    if (mToken != null) {
                        mToken.unlinkToDeath(mDeathRecipient, 0);
                    }
                } catch (NoSuchElementException e) {
                    // do nothing
                }
                return true;
            }
            return false;
        }
    }
}
