/*
 * Copyright (C) 2018 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 android.permission;

import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.os.Build.VERSION_CODES.S;
import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;

import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.PropertyInvalidatedCache;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.permission.flags.Flags;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;

import com.android.internal.R;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.CollectionUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * System level service for accessing the permission capabilities of the platform.
 *
 * @hide
 */
@SystemApi
@SystemService(Context.PERMISSION_SERVICE)
public final class PermissionManager {
    private static final String LOG_TAG = PermissionManager.class.getName();

    /**
     * The permission is granted.
     */
    public static final int PERMISSION_GRANTED = 0;

    /**
     * The permission is denied. Applicable only to runtime permissions.
     * <p>
     * The app isn't expecting the permission to be denied so that a "no-op" action should be taken,
     * such as returning an empty result.
     */
    public static final int PERMISSION_SOFT_DENIED = 1;

    /**
     * The permission is denied.
     * <p>
     * The app should receive a {@code SecurityException}, or an error through a relevant callback.
     */
    public static final int PERMISSION_HARD_DENIED = 2;

    /** @hide */
    @IntDef(prefix = { "PERMISSION_" }, value = {
            PERMISSION_GRANTED,
            PERMISSION_SOFT_DENIED,
            PERMISSION_HARD_DENIED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface PermissionResult {}

    /**
     * The set of flags that indicate that a permission state has been explicitly set
     *
     * @hide
     */
    public static final int EXPLICIT_SET_FLAGS = FLAG_PERMISSION_USER_SET
            | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_POLICY_FIXED
            | FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT
            | FLAG_PERMISSION_GRANTED_BY_ROLE;

    /**
     * Activity action: Launch UI to review permission decisions.
     * <p>
     * <strong>Important:</strong>You must protect the activity that handles this action with the
     * {@link android.Manifest.permission#START_REVIEW_PERMISSION_DECISIONS} permission to ensure
     * that only the system can launch this activity. The system will not launch activities that are
     * not properly protected.
     * <p>
     * Input: Nothing.
     * </p>
     * <p>
     * Output: Nothing.
     * </p>
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS)
    public static final String ACTION_REVIEW_PERMISSION_DECISIONS =
            "android.permission.action.REVIEW_PERMISSION_DECISIONS";


    /** @hide */
    public static final String LOG_TAG_TRACE_GRANTS = "PermissionGrantTrace";

    /** @hide */
    public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
            "permissions revoked";
    /** @hide */
    public static final String KILL_APP_REASON_GIDS_CHANGED =
            "permission grant or revoke changed gids";

    private static final String SYSTEM_PKG = "android";

    /**
     * Refuse to install package if groups of permissions are bad
     * - Permission groups should only be shared between apps sharing a certificate
     * - If a permission belongs to a group that group should be defined
     *
     * @hide
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = S)
    public static final long CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS = 146211400;

    /**
     * The time to wait in between refreshing the exempted indicator role packages
     */
    private static final long EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS = 15000;

    private static long sLastIndicatorUpdateTime = -1;

    private static final int[] EXEMPTED_ROLES = {R.string.config_systemAmbientAudioIntelligence,
        R.string.config_systemUiIntelligence, R.string.config_systemAudioIntelligence,
        R.string.config_systemNotificationIntelligence, R.string.config_systemTextIntelligence,
        R.string.config_systemVisualIntelligence};

    private static final String[] INDICATOR_EXEMPTED_PACKAGES = new String[EXEMPTED_ROLES.length];

    /**
     * Note: Changing this won't do anything on its own - you should also change the filtering in
     * {@link #shouldTraceGrant}.
     *
     * See log output for tag {@link #LOG_TAG_TRACE_GRANTS}
     *
     * @hide
     */
    public static final boolean DEBUG_TRACE_GRANTS = false;
    /**
     * @hide
     */
    public static final boolean DEBUG_TRACE_PERMISSION_UPDATES = false;

    /**
     * Additional debug log for virtual device permissions.
     * @hide
     */
    public static final boolean DEBUG_DEVICE_PERMISSIONS = false;

    /**
     * Intent extra: List of PermissionGroupUsages
     * <p>
     * Type: {@code List<PermissionGroupUsage>}
     * </p>
     * @hide
     */
    @SystemApi
    public static final String EXTRA_PERMISSION_USAGES =
            "android.permission.extra.PERMISSION_USAGES";

    /**
     * Specify what permissions are device aware. Only device aware permissions can be granted to
     * a remote device.
     * @hide
     */
    public static final Set<String> DEVICE_AWARE_PERMISSIONS =
            Flags.deviceAwarePermissionsEnabled()
                    ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
                    : Collections.emptySet();

    private final @NonNull Context mContext;

    private final IPackageManager mPackageManager;

    private final IPermissionManager mPermissionManager;

    private final LegacyPermissionManager mLegacyPermissionManager;

    private final ArrayMap<PackageManager.OnPermissionsChangedListener,
            IOnPermissionsChangeListener> mPermissionListeners = new ArrayMap<>();
    private PermissionUsageHelper mUsageHelper;

    private List<SplitPermissionInfo> mSplitPermissionInfos;

    /**
     * Creates a new instance.
     *
     * @param context The current context in which to operate
     *
     * @hide
     */
    public PermissionManager(@NonNull Context context)
            throws ServiceManager.ServiceNotFoundException {
        mContext = context;
        mPackageManager = AppGlobals.getPackageManager();
        mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
                "permissionmgr"));
        mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class);
    }

    /**
     * Checks whether a given data access chain described by the given {@link AttributionSource}
     * has a given permission.
     *
     * <strong>NOTE:</strong> Use this method only for permission checks at the
     * point where you will deliver the permission protected data to clients.
     *
     * <p>For example, if an app registers a location listener it should have the location
     * permission but no data is actually sent to the app at the moment of registration
     * and you should use {@link #checkPermissionForPreflight(String, AttributionSource)}
     * to determine if the app has or may have location permission (if app has only foreground
     * location the grant state depends on the app's fg/gb state) and this check will not
     * leave a trace that permission protected data was delivered. When you are about to
     * deliver the location data to a registered listener you should use this method which
     * will evaluate the permission access based on the current fg/bg state of the app and
     * leave a record that the data was accessed.
     *
     * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS
     * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the
     * accesses will have the UNTRUSTED flags.
     *
     * @param permission The permission to check.
     * @param attributionSource the permission identity
     * @param message A message describing the reason the permission was checked
     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
     *
     * @see #checkPermissionForPreflight(String, AttributionSource)
     */
    @PermissionResult
    @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true)
    public int checkPermissionForDataDelivery(@NonNull String permission,
            @NonNull AttributionSource attributionSource, @Nullable String message) {
        return PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
                // FIXME(b/199526514): PID should be passed inside AttributionSource.
                PermissionChecker.PID_UNKNOWN, attributionSource, message);
    }

    /**
     *
     * Similar to checkPermissionForDataDelivery, except it results in an app op start, rather than
     * a note. If this method is used, then {@link #finishDataDelivery(String, AttributionSource)}
     * must be used when access is finished.
     *
     * @param permission The permission to check.
     * @param attributionSource the permission identity
     * @param message A message describing the reason the permission was checked
     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
     *
     * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS
     * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the
     * accesses will have the UNTRUSTED flags.
     *
     * @see #checkPermissionForDataDelivery(String, AttributionSource, String)
     */
    @PermissionResult
    @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true)
    public int checkPermissionForStartDataDelivery(@NonNull String permission,
            @NonNull AttributionSource attributionSource, @Nullable String message) {
        return PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
                // FIXME(b/199526514): PID should be passed inside AttributionSource.
                PermissionChecker.PID_UNKNOWN, attributionSource, message, true);
    }

    /**
     * Indicate that usage has finished for an {@link AttributionSource} started with
     * {@link #checkPermissionForStartDataDelivery(String, AttributionSource, String)}
     *
     * @param permission The permission to check.
     * @param attributionSource the permission identity to finish
     */
    public void finishDataDelivery(@NonNull String permission,
            @NonNull AttributionSource attributionSource) {
        PermissionChecker.finishDataDelivery(mContext, AppOpsManager.permissionToOp(permission),
                attributionSource);
    }

    /**
     * Checks whether a given data access chain described by the given {@link AttributionSource}
     * has a given permission. Call this method if you are the datasource which would not blame you
     * for access to the data since you are the data. Use this API if you are the datasource of the
     * protected state.
     *
     * <strong>NOTE:</strong> Use this method only for permission checks at the
     * point where you will deliver the permission protected data to clients.
     *
     * <p>For example, if an app registers a location listener it should have the location
     * permission but no data is actually sent to the app at the moment of registration
     * and you should use {@link #checkPermissionForPreflight(String, AttributionSource)}
     * to determine if the app has or may have location permission (if app has only foreground
     * location the grant state depends on the app's fg/gb state) and this check will not
     * leave a trace that permission protected data was delivered. When you are about to
     * deliver the location data to a registered listener you should use this method which
     * will evaluate the permission access based on the current fg/bg state of the app and
     * leave a record that the data was accessed.
     *
     * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS
     * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the
     * accesses will have the UNTRUSTED flags.
     *
     * @param permission The permission to check.
     * @param attributionSource the permission identity
     * @param message A message describing the reason the permission was checked
     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
     *
     * @see #checkPermissionForPreflight(String, AttributionSource)
     */
    @PermissionResult
    @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true)
    public int checkPermissionForDataDeliveryFromDataSource(@NonNull String permission,
            @NonNull AttributionSource attributionSource, @Nullable String message) {
        return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext, permission,
                PermissionChecker.PID_UNKNOWN, attributionSource, message);
    }

    /**
     * Checks whether a given data access chain described by the given {@link AttributionSource}
     * has a given permission.
     *
     * <strong>NOTE:</strong> Use this method only for permission checks at the
     * preflight point where you will not deliver the permission protected data
     * to clients but schedule permission data delivery, apps register listeners,
     * etc.
     *
     * <p>For example, if an app registers a data listener it should have the required
     * permission but no data is actually sent to the app at the moment of registration
     * and you should use this method to determine if the app has or may have the
     * permission and this check will not leave a trace that permission protected data
     * was delivered. When you are about to deliver the protected data to a registered
     * listener you should use {@link #checkPermissionForDataDelivery(String,
     * AttributionSource, String)} which will evaluate the permission access based
     * on the current fg/bg state of the app and leave a record that the data was accessed.
     *
     * @param permission The permission to check.
     * @param attributionSource The identity for which to check the permission.
     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
     */
    @PermissionResult
    public int checkPermissionForPreflight(@NonNull String permission,
            @NonNull AttributionSource attributionSource) {
        return PermissionChecker.checkPermissionForPreflight(mContext, permission,
                attributionSource);
    }

    /**
     * Retrieve all of the information we know about a particular permission.
     *
     * @param permissionName the fully qualified name (e.g. com.android.permission.LOGIN) of the
     *                       permission you are interested in
     * @param flags additional option flags to modify the data returned
     * @return a {@link PermissionInfo} containing information about the permission, or {@code null}
     *         if not found
     *
     * @hide Pending API
     */
    @Nullable
    public PermissionInfo getPermissionInfo(@NonNull String permissionName,
            @PackageManager.PermissionInfoFlags int flags) {
        try {
            final String packageName = mContext.getOpPackageName();
            return mPermissionManager.getPermissionInfo(permissionName, packageName, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Query for all of the permissions associated with a particular group.
     *
     * @param groupName the fully qualified name (e.g. com.android.permission.LOGIN) of the
     *                  permission group you are interested in. Use {@code null} to find all of the
     *                  permissions not associated with a group
     * @param flags additional option flags to modify the data returned
     * @return a list of {@link PermissionInfo} containing information about all of the permissions
     *         in the given group, or {@code null} if the group is not found
     *
     * @hide Pending API
     */
    @Nullable
    public List<PermissionInfo> queryPermissionsByGroup(@Nullable String groupName,
            @PackageManager.PermissionInfoFlags int flags) {
        try {
            final ParceledListSlice<PermissionInfo> parceledList =
                    mPermissionManager.queryPermissionsByGroup(groupName, flags);
            if (parceledList == null) {
                return null;
            }
            return parceledList.getList();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Add a new dynamic permission to the system. For this to work, your package must have defined
     * a permission tree through the
     * {@link android.R.styleable#AndroidManifestPermissionTree &lt;permission-tree&gt;} tag in its
     * manifest. A package can only add permissions to trees that were defined by either its own
     * package or another with the same user id; a permission is in a tree if it matches the name of
     * the permission tree + ".": for example, "com.foo.bar" is a member of the permission tree
     * "com.foo".
     * <p>
     * It is good to make your permission tree name descriptive, because you are taking possession
     * of that entire set of permission names. Thus, it must be under a domain you control, with a
     * suffix that will not match any normal permissions that may be declared in any applications
     * that are part of that domain.
     * <p>
     * New permissions must be added before any .apks are installed that use those permissions.
     * Permissions you add through this method are remembered across reboots of the device. If the
     * given permission already exists, the info you supply here will be used to update it.
     *
     * @param permissionInfo description of the permission to be added
     * @param async whether the persistence of the permission should be asynchronous, allowing it to
     *              return quicker and batch a series of adds, at the expense of no guarantee the
     *              added permission will be retained if the device is rebooted before it is
     *              written.
     * @return {@code true} if a new permission was created, {@code false} if an existing one was
     *         updated
     * @throws SecurityException if you are not allowed to add the given permission name
     *
     * @see #removePermission(String)
     *
     * @hide Pending API
     */
    public boolean addPermission(@NonNull PermissionInfo permissionInfo, boolean async) {
        try {
            return mPermissionManager.addPermission(permissionInfo, async);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Removes a permission that was previously added with
     * {@link #addPermission(PermissionInfo, boolean)}. The same ownership rules apply -- you are
     * only allowed to remove permissions that you are allowed to add.
     *
     * @param permissionName the name of the permission to remove
     * @throws SecurityException if you are not allowed to remove the given permission name
     *
     * @see #addPermission(PermissionInfo, boolean)
     *
     * @hide Pending API
     */
    public void removePermission(@NonNull String permissionName) {
        try {
            mPermissionManager.removePermission(permissionName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieve all of the information we know about a particular group of permissions.
     *
     * @param groupName the fully qualified name (e.g. com.android.permission_group.APPS) of the
     *                  permission you are interested in
     * @param flags additional option flags to modify the data returned
     * @return a {@link PermissionGroupInfo} containing information about the permission, or
     *         {@code null} if not found
     *
     * @hide Pending API
     */
    @Nullable
    public PermissionGroupInfo getPermissionGroupInfo(@NonNull String groupName,
            @PackageManager.PermissionGroupInfoFlags int flags) {
        try {
            return mPermissionManager.getPermissionGroupInfo(groupName, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieve all of the known permission groups in the system.
     *
     * @param flags additional option flags to modify the data returned
     * @return a list of {@link PermissionGroupInfo} containing information about all of the known
     *         permission groups
     *
     * @hide Pending API
     */
    @NonNull
    public List<PermissionGroupInfo> getAllPermissionGroups(
            @PackageManager.PermissionGroupInfoFlags int flags) {
        try {
            final ParceledListSlice<PermissionGroupInfo> parceledList =
                    mPermissionManager.getAllPermissionGroups(flags);
            if (parceledList == null) {
                return Collections.emptyList();
            }
            return parceledList.getList();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Checks whether a particular permissions has been revoked for a package by policy. Typically
     * the device owner or the profile owner may apply such a policy. The user cannot grant policy
     * revoked permissions, hence the only way for an app to get such a permission is by a policy
     * change.
     *
     * @param packageName the name of the package you are checking against
     * @param permissionName the name of the permission you are checking for
     *
     * @return whether the permission is restricted by policy
     *
     * @hide Pending API
     */
    @CheckResult
    public boolean isPermissionRevokedByPolicy(@NonNull String packageName,
            @NonNull String permissionName) {
        try {
            return mPermissionManager.isPermissionRevokedByPolicy(packageName, permissionName,
                    mContext.getDeviceId(), mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** @hide */
    public static boolean shouldTraceGrant(
            @NonNull String packageName, @NonNull String permissionName, int userId) {
        // To be modified when debugging
        // template: if ("".equals(packageName) && "".equals(permissionName)) return true;
        return false;
    }

    /**
     * Grant a runtime permission to an application which the application does not already have. The
     * permission must have been requested by the application. If the application is not allowed to
     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
     * <p>
     * <strong>Note: </strong>Using this API requires holding
     * {@code android.permission.GRANT_RUNTIME_PERMISSIONS} and if the user ID is not the current
     * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
     *
     * @param packageName the package to which to grant the permission
     * @param permissionName the permission name to grant
     * @param user the user for which to grant the permission
     *
     * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String)
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
    //@SystemApi
    public void grantRuntimePermission(@NonNull String packageName,
            @NonNull String permissionName, @NonNull UserHandle user) {
        String persistentDeviceId = getPersistentDeviceId(mContext.getDeviceId());
        if (persistentDeviceId == null) {
            return;
        }

        grantRuntimePermissionInternal(packageName, permissionName, persistentDeviceId, user);
    }

    /**
     * Grant a runtime permission to an application which the application does not already have. The
     * permission must have been requested by the application. If the application is not allowed to
     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
     *
     * @param packageName the package to which to grant the permission
     * @param permissionName the permission name to grant
     * @param persistentDeviceId the device Id to which to grant the permission
     *
     * @see #revokeRuntimePermission(String, String, String, String)
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
    @SystemApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public void grantRuntimePermission(@NonNull String packageName,
            @NonNull String permissionName, @NonNull String persistentDeviceId) {
        grantRuntimePermissionInternal(packageName, permissionName, persistentDeviceId,
                mContext.getUser());
    }

    private void grantRuntimePermissionInternal(@NonNull String packageName,
            @NonNull String permissionName, @NonNull String persistentDeviceId,
            @NonNull UserHandle user) {
        if (DEBUG_TRACE_GRANTS
                && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
            Log.i(LOG_TAG_TRACE_GRANTS, "App " + mContext.getPackageName() + " is granting "
                    + packageName + " "
                    + permissionName + " for user " + user.getIdentifier()
                    + " for persistent device " + persistentDeviceId, new RuntimeException());
        }
        try {
            mPermissionManager.grantRuntimePermission(packageName, permissionName,
                    persistentDeviceId, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Revoke a runtime permission that was previously granted by
     * {@link #grantRuntimePermission(String, String, android.os.UserHandle)}. The permission must
     * have been requested by and granted to the application. If the application is not allowed to
     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
     * <p>
     * <strong>Note: </strong>Using this API requires holding
     * {@code android.permission.REVOKE_RUNTIME_PERMISSIONS} and if the user ID is not the current
     * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
     *
     * @param packageName the package from which to revoke the permission
     * @param permissionName the permission name to revoke
     * @param user the user for which to revoke the permission
     * @param reason the reason for the revoke, or {@code null} for unspecified
     *
     * @see #grantRuntimePermission(String, String, android.os.UserHandle)
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
    //@SystemApi
    public void revokeRuntimePermission(@NonNull String packageName,
            @NonNull String permissionName, @NonNull UserHandle user, @Nullable String reason) {
        String persistentDeviceId = getPersistentDeviceId(mContext.getDeviceId());
        if (persistentDeviceId == null) {
            return;
        }

        revokeRuntimePermissionInternal(packageName, permissionName, persistentDeviceId, user,
                reason);
    }

    /**
     * Revoke a runtime permission that was previously granted by
     * {@link #grantRuntimePermission(String, String, String)}. The permission must
     * have been requested by and granted to the application. If the application is not allowed to
     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
     *
     * @param packageName the package from which to revoke the permission
     * @param permissionName the permission name to revoke
     * @param persistentDeviceId the persistent device id for which to revoke the permission
     * @param reason the reason for the revoke, or {@code null} for unspecified
     *
     * @see #grantRuntimePermission(String, String, String)
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
    @SystemApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public void revokeRuntimePermission(@NonNull String packageName,
            @NonNull String permissionName, @NonNull String persistentDeviceId,
            @Nullable String reason) {
        revokeRuntimePermissionInternal(packageName, permissionName, persistentDeviceId,
                mContext.getUser(), reason);
    }

    private void revokeRuntimePermissionInternal(@NonNull String packageName,
            @NonNull String permissionName, @NonNull String persistentDeviceId,
            @NonNull UserHandle user, @Nullable String reason) {
        if (DEBUG_TRACE_PERMISSION_UPDATES
                && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
            Log.i(LOG_TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
                    + permissionName + " for user " + user.getIdentifier()
                    + " for persistent device "
                    + persistentDeviceId + " with reason "
                    + reason, new RuntimeException());
        }
        try {
            mPermissionManager.revokeRuntimePermission(packageName, permissionName,
                    persistentDeviceId, user.getIdentifier(), reason);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Gets the state flags associated with a permission.
     *
     * @param packageName the package name for which to get the flags
     * @param permissionName the permission for which to get the flags
     * @param user the user for which to get permission flags
     * @return the permission flags
     *
     * @hide
     */
    @PackageManager.PermissionFlags
    @RequiresPermission(anyOf = {
            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
            android.Manifest.permission.GET_RUNTIME_PERMISSIONS
    })
    //@SystemApi
    public int getPermissionFlags(@NonNull String packageName, @NonNull String permissionName,
            @NonNull UserHandle user) {
        String persistentDeviceId = getPersistentDeviceId(mContext.getDeviceId());
        if (persistentDeviceId == null) {
            return 0;
        }

        return getPermissionFlagsInternal(packageName, permissionName, persistentDeviceId, user);
    }

    /**
     * Gets the state flags associated with a permission.
     *
     * @param packageName the package name for which to get the flags
     * @param permissionName the permission for which to get the flags
     * @param persistentDeviceId the persistent device Id for which to get permission flags
     * @return the permission flags
     *
     * @hide
     */
    @PackageManager.PermissionFlags
    @RequiresPermission(anyOf = {
            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
            android.Manifest.permission.GET_RUNTIME_PERMISSIONS
    })
    @SystemApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public int getPermissionFlags(@NonNull String packageName, @NonNull String permissionName,
            @NonNull String persistentDeviceId) {
        return getPermissionFlagsInternal(packageName, permissionName, persistentDeviceId,
                mContext.getUser());
    }

    private int getPermissionFlagsInternal(@NonNull String packageName,
            @NonNull String permissionName, @NonNull String persistentDeviceId,
            @NonNull UserHandle user) {
        try {
            return mPermissionManager.getPermissionFlags(packageName, permissionName,
                    persistentDeviceId, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Updates the flags associated with a permission by replacing the flags in the specified mask
     * with the provided flag values.
     *
     * @param packageName The package name for which to update the flags
     * @param permissionName The permission for which to update the flags
     * @param flagMask The flags which to replace
     * @param flagValues The flags with which to replace
     * @param user The user for which to update the permission flags
     *
     * @hide
     */
    @RequiresPermission(anyOf = {
            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
    })
    //@SystemApi
    public void updatePermissionFlags(@NonNull String packageName, @NonNull String permissionName,
            @PackageManager.PermissionFlags int flagMask,
            @PackageManager.PermissionFlags int flagValues, @NonNull UserHandle user) {
        String persistentDeviceId = getPersistentDeviceId(mContext.getDeviceId());
        if (persistentDeviceId == null) {
            return;
        }

        updatePermissionFlagsInternal(packageName, permissionName, flagMask, flagValues,
                persistentDeviceId, user);
    }

    /**
     * Updates the flags associated with a permission by replacing the flags in the specified mask
     * with the provided flag values.
     *
     * @param packageName The package name for which to update the flags
     * @param permissionName The permission for which to update the flags
     * @param persistentDeviceId The persistent device for which to update the permission flags
     * @param flagMask The flags which to replace
     * @param flagValues The flags with which to replace
     *
     * @hide
     */
    @RequiresPermission(anyOf = {
            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
    })
    @SystemApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public void updatePermissionFlags(@NonNull String packageName, @NonNull String permissionName,
            @NonNull String persistentDeviceId,
            @PackageManager.PermissionFlags int flagMask,
            @PackageManager.PermissionFlags int flagValues
    ) {
        updatePermissionFlagsInternal(packageName, permissionName, flagMask, flagValues,
                persistentDeviceId, mContext.getUser());
    }

    private void updatePermissionFlagsInternal(@NonNull String packageName,
            @NonNull String permissionName,
            @PackageManager.PermissionFlags int flagMask,
            @PackageManager.PermissionFlags int flagValues, @NonNull String persistentDeviceId,
            @NonNull UserHandle user
    ) {
        if (DEBUG_TRACE_PERMISSION_UPDATES && shouldTraceGrant(packageName, permissionName,
                user.getIdentifier())) {
            Log.i(LOG_TAG, "App " + mContext.getPackageName() + " is updating flags for "
                    + packageName + " " + permissionName + " for user "
                    + user.getIdentifier() + " for persistentDeviceId " + persistentDeviceId + ": "
                    + DebugUtils.flagsToString(PackageManager.class, "FLAG_PERMISSION_", flagMask)
                    + " := " + DebugUtils.flagsToString(PackageManager.class, "FLAG_PERMISSION_",
                            flagValues), new RuntimeException());
        }
        try {
            final boolean checkAdjustPolicyFlagPermission =
                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q;
            mPermissionManager.updatePermissionFlags(packageName, permissionName, flagMask,
                    flagValues, checkAdjustPolicyFlagPermission,
                    persistentDeviceId, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Gets the restricted permissions that have been allowlisted and the app is allowed to have
     * them granted in their full form.
     * <p>
     * Permissions can be hard restricted which means that the app cannot hold them or soft
     * restricted where the app can hold the permission but in a weaker form. Whether a permission
     * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or
     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission
     * declaration. Allowlisting a hard restricted permission allows for the to hold that permission
     * and allowlisting a soft restricted permission allows the app to hold the permission in its
     * full, unrestricted form.
     * <p>
     * There are four allowlists:
     * <ol>
     * <li>
     * One for cases where the system permission policy allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be
     * accessed by pre-installed holders of a dedicated permission.
     * <li>
     * One for cases where the system allowlists the permission when upgrading from an OS version in
     * which the permission was not restricted to an OS version in which the permission is
     * restricted. This list corresponds to the
     * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by
     * pre-installed holders of a dedicated permission or the installer on record.
     * <li>
     * One for cases where the installer of the package allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
     * accessed by pre-installed holders of a dedicated permission or the installer on record.
     * </ol>
     *
     * @param packageName the app for which to get allowlisted permissions
     * @param allowlistFlag the flag to determine which allowlist to query. Only one flag can be
     *                      passed.
     * @return the allowlisted permissions that are on any of the allowlists you query for
     * @throws SecurityException if you try to access a allowlist that you have no access to
     *
     * @see #addAllowlistedRestrictedPermission(String, String, int)
     * @see #removeAllowlistedRestrictedPermission(String, String, int)
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
     *
     * @hide Pending API
     */
    @NonNull
    @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
            conditional = true)
    public Set<String> getAllowlistedRestrictedPermissions(@NonNull String packageName,
            @PackageManager.PermissionWhitelistFlags int allowlistFlag) {
        try {
            final List<String> allowlist = mPermissionManager.getAllowlistedRestrictedPermissions(
                    packageName, allowlistFlag, mContext.getUserId());
            if (allowlist == null) {
                return Collections.emptySet();
            }
            return new ArraySet<>(allowlist);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Adds a allowlisted restricted permission for an app.
     * <p>
     * Permissions can be hard restricted which means that the app cannot hold them or soft
     * restricted where the app can hold the permission but in a weaker form. Whether a permission
     * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or
     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission
     * declaration. Allowlisting a hard restricted permission allows for the to hold that permission
     * and allowlisting a soft restricted permission allows the app to hold the permission in its
     * full, unrestricted form.
     * <p>There are four allowlists:
     * <ol>
     * <li>
     * One for cases where the system permission policy allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be
     * accessed by pre-installed holders of a dedicated permission.
     * <li>
     * One for cases where the system allowlists the permission when upgrading from an OS version in
     * which the permission was not restricted to an OS version in which the permission is
     * restricted. This list corresponds to the
     * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by
     * pre-installed holders of a dedicated permission or the installer on record.
     * <li>
     * One for cases where the installer of the package allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
     * accessed by pre-installed holders of a dedicated permission or the installer on record.
     * </ol>
     * <p>
     * You need to specify the allowlists for which to set the allowlisted permissions which will
     * clear the previous allowlisted permissions and replace them with the provided ones.
     *
     * @param packageName the app for which to get allowlisted permissions
     * @param permissionName the allowlisted permission to add
     * @param allowlistFlags the allowlists to which to add. Passing multiple flags updates all
     *                       specified allowlists.
     * @return whether the permission was added to the allowlist
     * @throws SecurityException if you try to modify a allowlist that you have no access to.
     *
     * @see #getAllowlistedRestrictedPermissions(String, int)
     * @see #removeAllowlistedRestrictedPermission(String, String, int)
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
     *
     * @hide Pending API
     */
    @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
            conditional = true)
    public boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
            @NonNull String permissionName,
            @PackageManager.PermissionWhitelistFlags int allowlistFlags) {
        try {
            return mPermissionManager.addAllowlistedRestrictedPermission(packageName,
                    permissionName, allowlistFlags, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Removes a allowlisted restricted permission for an app.
     * <p>
     * Permissions can be hard restricted which means that the app cannot hold them or soft
     * restricted where the app can hold the permission but in a weaker form. Whether a permission
     * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or
     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission
     * declaration. Allowlisting a hard restricted permission allows for the to hold that permission
     * and allowlisting a soft restricted permission allows the app to hold the permission in its
     * full, unrestricted form.
     * <p>There are four allowlists:
     * <ol>
     * <li>
     * One for cases where the system permission policy allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be
     * accessed by pre-installed holders of a dedicated permission.
     * <li>
     * One for cases where the system allowlists the permission when upgrading from an OS version in
     * which the permission was not restricted to an OS version in which the permission is
     * restricted. This list corresponds to the
     * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by
     * pre-installed holders of a dedicated permission or the installer on record.
     * <li>
     * One for cases where the installer of the package allowlists a permission. This list
     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
     * accessed by pre-installed holders of a dedicated permission or the installer on record.
     * </ol>
     * <p>
     * You need to specify the allowlists for which to set the allowlisted permissions which will
     * clear the previous allowlisted permissions and replace them with the provided ones.
     *
     * @param packageName the app for which to get allowlisted permissions
     * @param permissionName the allowlisted permission to remove
     * @param allowlistFlags the allowlists from which to remove. Passing multiple flags updates all
     *                       specified allowlists.
     * @return whether the permission was removed from the allowlist
     * @throws SecurityException if you try to modify a allowlist that you have no access to.
     *
     * @see #getAllowlistedRestrictedPermissions(String, int)
     * @see #addAllowlistedRestrictedPermission(String, String, int)
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
     * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
     *
     * @hide Pending API
     */
    @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
            conditional = true)
    public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
            @NonNull String permissionName,
            @PackageManager.PermissionWhitelistFlags int allowlistFlags) {
        try {
            return mPermissionManager.removeAllowlistedRestrictedPermission(packageName,
                    permissionName, allowlistFlags, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Checks whether an application is exempted from having its permissions be automatically
     * revoked when the app is unused for an extended period of time.
     * <p>
     * Only the installer on record that installed the given package, or a holder of
     * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
     *
     * @param packageName the app for which to set exemption
     * @return whether the app is exempted
     * @throws SecurityException if you you have no access to this
     *
     * @see #setAutoRevokeExempted
     *
     * @hide Pending API
     */
    @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
            conditional = true)
    public boolean isAutoRevokeExempted(@NonNull String packageName) {
        try {
            return mPermissionManager.isAutoRevokeExempted(packageName, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Marks an application exempted from having its permissions be automatically revoked when the
     * app is unused for an extended period of time.
     * <p>
     * Only the installer on record that installed the given package is allowed to call this.
     * <p>
     * Packages start in exempted state, and it is the installer's responsibility to un-exempt the
     * packages it installs, unless auto-revoking permissions from that package would cause
     * breakages beyond having to re-request the permission(s).
     *
     * @param packageName the app for which to set exemption
     * @param exempted whether the app should be exempted
     * @return whether any change took effect
     * @throws SecurityException if you you have no access to modify this
     *
     * @see #isAutoRevokeExempted
     *
     * @hide Pending API
     */
    @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
            conditional = true)
    public boolean setAutoRevokeExempted(@NonNull String packageName, boolean exempted) {
        try {
            return mPermissionManager.setAutoRevokeExempted(packageName, exempted,
                    mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get whether you should show UI with rationale for requesting a permission. You should do this
     * only if you do not have the permission and the context in which the permission is requested
     * does not clearly communicate to the user what would be the benefit from grating this
     * permission.
     *
     * @param permissionName a permission your app wants to request
     * @return whether you can show permission rationale UI
     *
     * @hide
     */
    //@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    public boolean shouldShowRequestPermissionRationale(@NonNull String permissionName) {
        try {
            final String packageName = mContext.getPackageName();
            return mPermissionManager.shouldShowRequestPermissionRationale(packageName,
                    permissionName, mContext.getDeviceId(), mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Add a listener for permission changes for installed packages.
     *
     * @param listener the listener to add
     *
     * @hide
     */
    //@SystemApi
    @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
    public void addOnPermissionsChangeListener(
            @NonNull PackageManager.OnPermissionsChangedListener listener) {
        synchronized (mPermissionListeners) {
            if (mPermissionListeners.get(listener) != null) {
                return;
            }
            final OnPermissionsChangeListenerDelegate delegate =
                    new OnPermissionsChangeListenerDelegate(listener, Looper.getMainLooper());
            try {
                mPermissionManager.addOnPermissionsChangeListener(delegate);
                mPermissionListeners.put(listener, delegate);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Remove a listener for permission changes for installed packages.
     *
     * @param listener the listener to remove
     *
     * @hide
     */
    //@SystemApi
    @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
    public void removeOnPermissionsChangeListener(
            @NonNull PackageManager.OnPermissionsChangedListener listener) {
        synchronized (mPermissionListeners) {
            final IOnPermissionsChangeListener delegate = mPermissionListeners.get(listener);
            if (delegate != null) {
                try {
                    mPermissionManager.removeOnPermissionsChangeListener(delegate);
                    mPermissionListeners.remove(listener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    }

    /**
     * Gets the version of the runtime permission database.
     *
     * @return The database version, -1 when this is an upgrade from pre-Q, 0 when this is a fresh
     * install.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(anyOf = {
            Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
            Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS
    })
    public @IntRange(from = 0) int getRuntimePermissionsVersion() {
        try {
            return mPackageManager.getRuntimePermissionsVersion(mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Sets the version of the runtime permission database.
     *
     * @param version The new version.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(anyOf = {
            Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
            Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS
    })
    public void setRuntimePermissionsVersion(@IntRange(from = 0) int version) {
        try {
            mPackageManager.setRuntimePermissionsVersion(version, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get set of permissions that have been split into more granular or dependent permissions.
     *
     * <p>E.g. before {@link android.os.Build.VERSION_CODES#Q} an app that was granted
     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access the location while it was in
     * foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#Q}
     * the location permission only grants location access while the app is in foreground. This
     * would break apps that target before {@link android.os.Build.VERSION_CODES#Q}. Hence whenever
     * such an old app asks for a location permission (i.e. the
     * {@link SplitPermissionInfo#getSplitPermission()}), then the
     * {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside
     * {@link SplitPermissionInfo#getNewPermissions}) is added.
     *
     * <p>Note: Regular apps do not have to worry about this. The platform and permission controller
     * automatically add the new permissions where needed.
     *
     * @return All permissions that are split.
     */
    public @NonNull List<SplitPermissionInfo> getSplitPermissions() {
        if (mSplitPermissionInfos != null) {
            return mSplitPermissionInfos;
        }

        List<SplitPermissionInfoParcelable> parcelableList;
        try {
            parcelableList = ActivityThread.getPermissionManager().getSplitPermissions();
        } catch (RemoteException e) {
            Slog.e(LOG_TAG, "Error getting split permissions", e);
            return Collections.emptyList();
        }

        mSplitPermissionInfos = splitPermissionInfoListToNonParcelableList(parcelableList);

        return mSplitPermissionInfos;
    }

    /**
     * Initialize the PermissionUsageHelper, which will register active app op listeners
     *
     * @hide
     */
    public void initializeUsageHelper() {
        if (mUsageHelper == null) {
            mUsageHelper = new PermissionUsageHelper(mContext);
        }
    }

    /**
     * Teardown the PermissionUsageHelper, removing listeners
     *
     * @hide
     */
    public void tearDownUsageHelper() {
        if (mUsageHelper != null) {
            mUsageHelper.tearDown();
            mUsageHelper = null;
        }
    }

    /**
     * @return A list of permission groups currently or recently used by all apps by all users in
     * the current profile group.
     *
     * @hide
     */
    @TestApi
    @NonNull
    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
    public List<PermissionGroupUsage> getIndicatorAppOpUsageData() {
        return getIndicatorAppOpUsageData(new AudioManager().isMicrophoneMute());
    }

    /**
     * @param micMuted whether to consider the microphone muted when retrieving audio ops
     * @return A list of permission groups currently or recently used by all apps by all users in
     * the current profile group.
     *
     * @hide
     */
    @TestApi
    @NonNull
    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
    public List<PermissionGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
        // Lazily initialize the usage helper
        initializeUsageHelper();
        boolean includeMicrophoneUsage = !micMuted;
        return mUsageHelper.getOpUsageDataForIndicatorsByDevice(includeMicrophoneUsage,
                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
    }

    /**
     * Determine if a package should be shown in indicators. Only a select few roles, and the
     * system app itself, are hidden. These values are updated at most every 15 seconds.
     * @hide
     */
    public static boolean shouldShowPackageForIndicatorCached(@NonNull Context context,
            @NonNull String packageName) {
        return !getIndicatorExemptedPackages(context).contains(packageName);
    }

    /**
     * Get the list of packages that are not shown by the indicators. Only a select few roles, and
     * the system app itself, are hidden. These values are updated at most every 15 seconds.
     * @hide
     */
    public static Set<String> getIndicatorExemptedPackages(@NonNull Context context) {
        updateIndicatorExemptedPackages(context);
        ArraySet<String> pkgNames = new ArraySet<>();
        pkgNames.add(SYSTEM_PKG);
        for (int i = 0; i < INDICATOR_EXEMPTED_PACKAGES.length; i++) {
            String exemptedPackage = INDICATOR_EXEMPTED_PACKAGES[i];
            if (exemptedPackage != null) {
                pkgNames.add(exemptedPackage);
            }
        }
        return pkgNames;
    }

    /**
     * Update the cached indicator exempted packages
     * @hide
     */
    public static void updateIndicatorExemptedPackages(@NonNull Context context) {
        long now = SystemClock.elapsedRealtime();
        if (sLastIndicatorUpdateTime == -1
                || (now - sLastIndicatorUpdateTime) > EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS) {
            sLastIndicatorUpdateTime = now;
            for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
                INDICATOR_EXEMPTED_PACKAGES[i] = context.getString(EXEMPTED_ROLES[i]);
            }
        }
    }
    /**
     * Gets the list of packages that have permissions that specified
     * {@code requestDontAutoRevokePermissions=true} in their
     * {@code application} manifest declaration.
     *
     * @return the list of packages for current user
     * @hide
     */
    @SystemApi
    @NonNull
    @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
    public Set<String> getAutoRevokeExemptionRequestedPackages() {
        try {
            return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionRequestedPackages(
                    mContext.getUser().getIdentifier()));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Gets the list of packages that have permissions that specified
     * {@code autoRevokePermissions=disallowed} in their
     * {@code application} manifest declaration.
     *
     * @return the list of packages for current user
     * @hide
     */
    @SystemApi
    @NonNull
    @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
    public Set<String> getAutoRevokeExemptionGrantedPackages() {
        try {
            return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionGrantedPackages(
                    mContext.getUser().getIdentifier()));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
            List<SplitPermissionInfoParcelable> parcelableList) {
        final int size = parcelableList.size();
        List<SplitPermissionInfo> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(new SplitPermissionInfo(parcelableList.get(i)));
        }
        return list;
    }

    /**
     * Converts a {@link List} of {@link SplitPermissionInfo} into a List of
     * {@link SplitPermissionInfoParcelable} and returns it.
     * @hide
     */
    public static List<SplitPermissionInfoParcelable> splitPermissionInfoListToParcelableList(
            List<SplitPermissionInfo> splitPermissionsList) {
        final int size = splitPermissionsList.size();
        List<SplitPermissionInfoParcelable> outList = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            SplitPermissionInfo info = splitPermissionsList.get(i);
            outList.add(new SplitPermissionInfoParcelable(
                    info.getSplitPermission(), info.getNewPermissions(), info.getTargetSdk()));
        }
        return outList;
    }

    /**
     * A permission that was added in a previous API level might have split into several
     * permissions. This object describes one such split.
     */
    @Immutable
    public static final class SplitPermissionInfo {
        private @NonNull final SplitPermissionInfoParcelable mSplitPermissionInfoParcelable;

        @Override
        public boolean equals(@Nullable Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            SplitPermissionInfo that = (SplitPermissionInfo) o;
            return mSplitPermissionInfoParcelable.equals(that.mSplitPermissionInfoParcelable);
        }

        @Override
        public int hashCode() {
            return mSplitPermissionInfoParcelable.hashCode();
        }

        /**
         * Get the permission that is split.
         */
        public @NonNull String getSplitPermission() {
            return mSplitPermissionInfoParcelable.getSplitPermission();
        }

        /**
         * Get the permissions that are added.
         */
        public @NonNull List<String> getNewPermissions() {
            return mSplitPermissionInfoParcelable.getNewPermissions();
        }

        /**
         * Get the target API level when the permission was split.
         */
        public int getTargetSdk() {
            return mSplitPermissionInfoParcelable.getTargetSdk();
        }

        /**
         * Constructs a split permission.
         *
         * @param splitPerm old permission that will be split
         * @param newPerms list of new permissions that {@code rootPerm} will be split into
         * @param targetSdk apps targetting SDK versions below this will have {@code rootPerm}
         * split into {@code newPerms}
         * @hide
         */
        public SplitPermissionInfo(@NonNull String splitPerm, @NonNull List<String> newPerms,
                int targetSdk) {
            this(new SplitPermissionInfoParcelable(splitPerm, newPerms, targetSdk));
        }

        private SplitPermissionInfo(@NonNull SplitPermissionInfoParcelable parcelable) {
            mSplitPermissionInfoParcelable = parcelable;
        }
    }

    /**
     * Starts a one-time permission session for a given package.
     * @see #startOneTimePermissionSession(String, long, long, int, int)
     * @hide
     * @deprecated Use {@link #startOneTimePermissionSession(String, long, long, int, int)} instead
     */
    @Deprecated
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
    public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis,
            @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
            @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
        startOneTimePermissionSession(packageName, timeoutMillis, -1,
                importanceToResetTimer, importanceToKeepSessionAlive);
    }

    /**
     * Starts a one-time permission session for a given package. A one-time permission session is
     * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
     * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
     * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
     * until going > importanceToResetTimer.
     * <p>
     * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
     * then the session is extended until either the importance goes above
     * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
     * will continue the session and reset the timer.
     * </p>
     * <p>
     * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
     * </p>
     * <p>
     * Once the session ends
     * {@link PermissionControllerService#onOneTimePermissionSessionTimeout(String)} is invoked.
     * </p>
     * <p>
     * Note that if there is currently an active session for a package a new one isn't created but
     * each parameter of the existing one will be updated to the more aggressive of both sessions.
     * This means that durations will be set to the shortest parameter and importances will be set
     * to the lowest one.
     * </p>
     * @param packageName The package to start a one-time permission session for
     * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
     * @param revokeAfterKilledDelayMillis Number of milliseconds to wait before revoking on the
     *                                     event an app is terminated. Set to -1 to use default
     *                                     value for the device.
     * @param importanceToResetTimer The least important level to uid must be to reset the timer
     * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
     *                                     session alive
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
    public void startOneTimePermissionSession(@NonNull String packageName,
            @DurationMillisLong long timeoutMillis,
            @DurationMillisLong long revokeAfterKilledDelayMillis,
            @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
            @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
        try {
            mPermissionManager.startOneTimePermissionSession(packageName, mContext.getDeviceId(),
                    mContext.getUserId(), timeoutMillis, revokeAfterKilledDelayMillis);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * Stops the one-time permission session for the package. The callback to the end of session is
     * not invoked. If there is no one-time session for the package then nothing happens.
     *
     * @param packageName Package to stop the one-time permission session for
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
    public void stopOneTimePermissionSession(@NonNull String packageName) {
        try {
            mPermissionManager.stopOneTimePermissionSession(packageName,
                    mContext.getUserId());
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * Checks whether the package with the given pid/uid can read device identifiers.
     *
     * @param packageName      the name of the package to be checked for identifier access
     * @param message          the message to be used for logging during identifier access
     *                         verification
     * @param callingFeatureId the feature in the package
     * @param pid              the process id of the package to be checked
     * @param uid              the uid of the package to be checked
     * @return {@link PackageManager#PERMISSION_GRANTED} if the package is allowed identifier
     * access, {@link PackageManager#PERMISSION_DENIED} otherwise
     * @hide
     */
    @SystemApi
    public int checkDeviceIdentifierAccess(@Nullable String packageName, @Nullable String message,
            @Nullable String callingFeatureId, int pid, int uid) {
        return mLegacyPermissionManager.checkDeviceIdentifierAccess(packageName, message,
                callingFeatureId, pid, uid);
    }

    /**
     * Registers an attribution source with the OS. An app can only register an attribution
     * source for itself. Once an attribution source has been registered another app can
     * check whether this registration exists and thus trust the payload in the source
     * object. This is important for permission checking and specifically for app op blaming
     * since a malicious app should not be able to force the OS to blame another app
     * that doesn't participate in an attribution chain.
     *
     * @param source The attribution source to register.
     * @return The registered new attribution source.
     *
     * @see #isRegisteredAttributionSource(AttributionSource)
     *
     * @hide
     */
    @TestApi
    public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
        // We use a shared static token for sources that are not registered since the token's
        // only used for process death detection. If we are about to use the source for security
        // enforcement we need to replace the binder with a unique one.
        try {
            IBinder newToken = mPermissionManager.registerAttributionSource(source.asState());
            return source.withToken(newToken);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
        return source;
    }

    /**
     * Checks whether an attribution source is registered.
     *
     * @param source The attribution source to check.
     * @return Whether this is a registered source.
     *
     * @see #registerAttributionSource(AttributionSource)
     *
     * @hide
     */
    @TestApi
    @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
    public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
        try {
            return mPermissionManager.isRegisteredAttributionSource(source.asState());
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
        return false;
    }

    /**
     * Gets the number of currently registered attribution sources for a particular UID. This should
     * only be used for testing purposes.
     * @hide
     */
    @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS)
    public int getRegisteredAttributionSourceCountForTest(int uid) {
        try {
            return mPermissionManager.getRegisteredAttributionSourceCount(uid);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
        return -1;
    }

    /**
     * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
     * USED in CTS or local tests.
     *
     * @param packageName The package to be revoked
     * @param userId The user for which to revoke
     *
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL)
    public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String packageName,
            int userId) {
        try {
            mPermissionManager.revokePostNotificationPermissionWithoutKillForTest(packageName,
                    userId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    // Only warn once for assuming that root or system UID has a permission
    // to reduce duplicate logcat output.
    private static volatile boolean sShouldWarnMissingActivityManager = true;

    private static int checkPermissionUncached(@Nullable String permission, int pid, int uid,
            int deviceId) {
        final IActivityManager am = ActivityManager.getService();
        if (am == null) {
            // We don't have an active ActivityManager instance and the calling UID is not root or
            // system, so we don't grant this permission.
            Slog.w(LOG_TAG, "Missing ActivityManager; assuming " + uid + " does not hold "
                    + permission);
            return PackageManager.PERMISSION_DENIED;
        }
        try {
            sShouldWarnMissingActivityManager = true;
            return am.checkPermissionForDevice(permission, pid, uid, deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private static int getPermissionRequestStateUncached(String packageName, String permission,
            int deviceId) {
        try {
            return AppGlobals.getPermissionManager().getPermissionRequestState(
                    packageName, permission, deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Identifies a permission query.
     *
     * N.B. we include the checking pid for tracking purposes but don't include it in the equality
     * comparison: we use only uid for the actual security check, so comparing pid would result
     * in spurious misses.
     *
     * @hide
     */
    @Immutable
    private static final class PermissionQuery {
        final String permission;
        final int pid;
        final int uid;
        final int deviceId;

        PermissionQuery(@Nullable String permission, int pid, int uid, int deviceId) {
            this.permission = permission;
            this.pid = pid;
            this.uid = uid;
            this.deviceId = deviceId;
        }

        @Override
        public String toString() {
            return TextUtils.formatSimple("PermissionQuery(permission=\"%s\", pid=%d, uid=%d, "
                            + "deviceId=%d)", permission, pid, uid, deviceId);
        }

        @Override
        public int hashCode() {
            // N.B. pid doesn't count toward equality and therefore shouldn't count for
            // hashing either.
            return Objects.hash(permission, uid, deviceId);
        }

        @Override
        public boolean equals(@Nullable Object rval) {
            // N.B. pid doesn't count toward equality!
            if (rval == null) {
                return false;
            }
            PermissionQuery other;
            try {
                other = (PermissionQuery) rval;
            } catch (ClassCastException ex) {
                return false;
            }
            return uid == other.uid && deviceId == other.deviceId
                    && Objects.equals(permission, other.permission);
        }
    }

    private static final class PermissionRequestStateQuery {
        final String mPackageName;
        final String mPermission;
        final int mDeviceId;

        PermissionRequestStateQuery(@NonNull String packageName, @NonNull String permission,
                int deviceId) {
            mPackageName = packageName;
            mPermission = permission;
            mDeviceId = deviceId;
        }

        @Override
        public String toString() {
            return TextUtils.formatSimple("PermissionRequestStateQuery(package=\"%s\","
                            + " permission=\"%s\", " + "deviceId=%d)",
                    mPackageName, mPermission, mDeviceId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mPackageName, mPermission, mDeviceId);
        }

        @Override
        public boolean equals(@Nullable Object rval) {
            if (rval == null) {
                return false;
            }
            PermissionRequestStateQuery other;
            try {
                other = (PermissionRequestStateQuery) rval;
            } catch (ClassCastException ex) {
                return false;
            }
            return mDeviceId == other.mDeviceId && Objects.equals(mPackageName, other.mPackageName)
                    && Objects.equals(mPermission, other.mPermission);
        }
    }

    /**
     * The PropertyInvalidatedCache key for invalidating caches.
     * @hide
     */
    public static final String CACHE_KEY_PACKAGE_INFO_CACHE =
            PropertyInvalidatedCache.createSystemCacheKey("package_info_cache");

    /** @hide */
    private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
            new PropertyInvalidatedCache<PermissionQuery, Integer>(
                    2048, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPermission") {
                @Override
                public Integer recompute(PermissionQuery query) {
                    return checkPermissionUncached(query.permission, query.pid, query.uid,
                            query.deviceId);
                }
            };

    /** @hide */
    private static final PropertyInvalidatedCache<PermissionRequestStateQuery, Integer>
            sPermissionRequestStateCache =
            new PropertyInvalidatedCache<>(
                    512, CACHE_KEY_PACKAGE_INFO_CACHE, "getPermissionRequestState") {
                @Override
                public Integer recompute(PermissionRequestStateQuery query) {
                    return getPermissionRequestStateUncached(query.mPackageName, query.mPermission,
                            query.mDeviceId);
                }
            };

    /** @hide */
    public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) {
        // Short-circuit the cache for unconditionally granted permissions. This was previously
        // behind the cache, but placing here avoids marginal cache query overhead in system server.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId));
    }

    /** @hide */
    @Context.PermissionRequestState
    public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission,
            int deviceId) {
        int resolvedDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permission);
        return sPermissionRequestStateCache.query(
                new PermissionRequestStateQuery(packageName, permission, resolvedDeviceId));
    }

    /**
     * Gets the permission states for requested package and persistent device.
     * <p>
     * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
     * exact permission states for the requested device.
     *
     * @param packageName name of the package you are checking against
     * @param persistentDeviceId id of the persistent device you are checking against
     * @return mapping of all permission states keyed by their permission names
     *
     * @hide
     */
    @SystemApi
    @NonNull
    @RequiresPermission(anyOf = {
            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
            android.Manifest.permission.GET_RUNTIME_PERMISSIONS
    })
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
            @NonNull String persistentDeviceId) {
        try {
            return mPermissionManager.getAllPermissionStates(packageName, persistentDeviceId,
                    mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Make checkPermission() above bypass the permission cache in this process.
     *
     * @hide
     */
    public static void disablePermissionCache() {
        sPermissionCache.disableLocal();
    }

    /**
     * Like PermissionQuery, but for permission checks based on a package name instead of
     * a UID.
     */
    @Immutable
    private static final class PackageNamePermissionQuery {
        final String permName;
        final String pkgName;
        final String persistentDeviceId;
        @UserIdInt
        final int userId;

        PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName,
                @Nullable String persistentDeviceId, @UserIdInt int userId) {
            this.permName = permName;
            this.pkgName = pkgName;
            this.persistentDeviceId = persistentDeviceId;
            this.userId = userId;
        }

        @Override
        public String toString() {
            return TextUtils.formatSimple(
                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s\", "
                            + "persistentDeviceId=%s, userId=%s\")",
                    pkgName, permName, persistentDeviceId, userId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(permName, pkgName, persistentDeviceId, userId);
        }

        @Override
        public boolean equals(@Nullable Object rval) {
            if (rval == null) {
                return false;
            }
            PackageNamePermissionQuery other;
            try {
                other = (PackageNamePermissionQuery) rval;
            } catch (ClassCastException ex) {
                return false;
            }
            return Objects.equals(permName, other.permName)
                    && Objects.equals(pkgName, other.pkgName)
                    && Objects.equals(persistentDeviceId, other.persistentDeviceId)
                    && userId == other.userId;
        }
    }

    /* @hide */
    private static int checkPackageNamePermissionUncached(
            String permName, String pkgName, String persistentDeviceId, @UserIdInt int userId) {
        try {
            return ActivityThread.getPermissionManager().checkPermission(
                    pkgName, permName, persistentDeviceId, userId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /* @hide */
    private static PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>
            sPackageNamePermissionCache =
            new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>(
                    16, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPackageNamePermission") {
                @Override
                public Integer recompute(PackageNamePermissionQuery query) {
                    return checkPackageNamePermissionUncached(
                            query.permName, query.pkgName, query.persistentDeviceId, query.userId);
                }
                @Override
                public boolean bypass(PackageNamePermissionQuery query) {
                    return query.userId < 0;
                }
            };

    /**
     * Check whether a package has a permission for given device.
     *
     * @hide
     */
    public int checkPackageNamePermission(String permName, String pkgName,
            int deviceId, @UserIdInt int userId) {
        int resolvedDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permName);
        String persistentDeviceId = getPersistentDeviceId(resolvedDeviceId);
        return sPackageNamePermissionCache.query(
                new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId));
    }

    /**
     * When checking a device-aware permission on a remote device, if the permission is CAMERA
     * or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
     * device doesn't have capability fall back to checking permission on the default device.
     *
     * @hide
     */
    public static int resolveDeviceIdForPermissionCheck(@NonNull Context context, int deviceId,
            @Nullable String permission) {
        if (deviceId == Context.DEVICE_ID_DEFAULT || permission == null
                || !DEVICE_AWARE_PERMISSIONS.contains(permission)) {
            return Context.DEVICE_ID_DEFAULT;
        }

        VirtualDeviceManager virtualDeviceManager =
                context.getSystemService(VirtualDeviceManager.class);
        if (virtualDeviceManager == null) {
            Slog.e(LOG_TAG, "VDM is not enabled when device id is not default. deviceId = "
                    + deviceId);
        } else {
            VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
            if (virtualDevice != null) {
                if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
                        && !virtualDevice.hasCustomAudioInputSupport())
                        || (Objects.equals(permission, Manifest.permission.CAMERA)
                        && !virtualDevice.hasCustomCameraSupport())) {
                    deviceId = Context.DEVICE_ID_DEFAULT;
                }
            } else {
                Slog.e(LOG_TAG,
                        "virtualDevice is not found when device id is not default. deviceId = "
                                + deviceId);
            }
        }
        return deviceId;
    }

    @Nullable
    private String getPersistentDeviceId(int deviceId) {
        String persistentDeviceId = null;

        if (deviceId == Context.DEVICE_ID_DEFAULT) {
            persistentDeviceId = PERSISTENT_DEVICE_ID_DEFAULT;
        } else {
            VirtualDeviceManager virtualDeviceManager = mContext.getSystemService(
                    VirtualDeviceManager.class);
            if (virtualDeviceManager != null) {
                VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
                if (virtualDevice == null) {
                    Slog.e(LOG_TAG, "Virtual device is not found with device Id " + deviceId);
                    return null;
                }
                persistentDeviceId = virtualDevice.getPersistentDeviceId();
                if (persistentDeviceId == null) {
                    Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId);
                }
            }
        }
        return persistentDeviceId;
    }

    /**
     * Check whether a package has been granted a permission on a given device.
     * <p>
     * <strong>Note: </strong>This API returns the underlying permission state
     * as-is and is mostly intended for permission managing system apps. To
     * perform an access check for a certain app, please use the
     * {@link Context#checkPermission} APIs instead.
     *
     * @param permissionName The name of the permission you are checking for.
     * @param packageName The name of the package you are checking against.
     * @param persistentDeviceId The id of the physical device that you are checking permission
     *                           against.
     *
     * @return If the package has the permission on the device, PERMISSION_GRANTED is
     * returned.  If it does not have the permission on the device, PERMISSION_DENIED
     * is returned.
     *
     * @see VirtualDevice#getPersistentDeviceId()
     * @see PackageManager#PERMISSION_GRANTED
     * @see PackageManager#PERMISSION_DENIED
     *
     * @hide
     */
    @SystemApi
    @PermissionResult
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public int checkPermission(@NonNull String permissionName, @NonNull String packageName,
            @NonNull String persistentDeviceId) {
        return sPackageNamePermissionCache.query(
                new PackageNamePermissionQuery(permissionName, packageName, persistentDeviceId,
                        mContext.getUserId()));
    }

    /**
     * Make checkPackageNamePermission() bypass the cache in this process.
     *
     * @hide
     */
    public static void disablePackageNamePermissionCache() {
        sPackageNamePermissionCache.disableLocal();
    }

    private final class OnPermissionsChangeListenerDelegate
            extends IOnPermissionsChangeListener.Stub implements Handler.Callback {
        private static final int MSG_PERMISSIONS_CHANGED = 1;

        private final PackageManager.OnPermissionsChangedListener mListener;
        private final Handler mHandler;

        public OnPermissionsChangeListenerDelegate(
                PackageManager.OnPermissionsChangedListener listener, Looper looper) {
            mListener = listener;
            mHandler = new Handler(looper, this);
        }

        @Override
        public void onPermissionsChanged(int uid, String persistentDeviceId) {
            mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId)
                    .sendToTarget();
        }

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_PERMISSIONS_CHANGED: {
                    final int uid = msg.arg1;
                    final String persistentDeviceId = msg.obj.toString();
                    mListener.onPermissionsChanged(uid, persistentDeviceId);
                    return true;
                }
                default:
                    return false;
            }
        }
    }

    /**
     * Data class for the state of a permission requested by a package
     *
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public static final class PermissionState implements Parcelable {
        private final boolean mGranted;
        private final int mFlags;

        /** @hide */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        public PermissionState(boolean granted, int flags) {
            mGranted = granted;
            mFlags = flags;
        }

        /**
         * Returns whether this permission is granted
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        public boolean isGranted() {
            return mGranted;
        }

        /**
         * Returns the flags associated with this permission state
         * @see PackageManager#getPermissionFlags
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        public int getFlags() {
            return mFlags;
        }

        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        @Override
        public int describeContents() {
            return 0;
        }

        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        @Override
        public void writeToParcel(@NonNull Parcel parcel, int flags) {
            parcel.writeBoolean(mGranted);
            parcel.writeInt(mFlags);
        }

        private PermissionState(Parcel parcel) {
            this(parcel.readBoolean(), parcel.readInt());
        }

        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        public static final @NonNull Creator<PermissionState> CREATOR = new Creator<>() {
            public PermissionState createFromParcel(Parcel source) {
                return new PermissionState(source);
            }

            public PermissionState[] newArray(int size) {
                return new PermissionState[size];
            }
        };

        /** @hide */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PermissionState that = (PermissionState) o;
            return mGranted == that.mGranted && mFlags == that.mFlags;
        }

        /** @hide */
        @Override
        public int hashCode() {
            return Objects.hash(mGranted, mFlags);
        }

        /** @hide */
        @Override
        public String toString() {
            return "PermissionState{"
                    + "mGranted=" + mGranted
                    + ", mFlags=" + mFlags
                    + '}';
        }
    }
}
