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

package com.android.server.policy;

import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.LegacyPermissionManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseLongArray;
import android.util.Slog;
import android.util.SparseBooleanArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.IntPair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.PermissionThread;
import com.android.server.SystemService;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo;
import com.android.server.wm.ActivityTaskManagerInternal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * This is a permission policy that governs over all permission mechanism
 * such as permissions, app ops, etc. For example, the policy ensures that
 * permission state and app ops is synchronized for cases where there is a
 * dependency between permission state (permissions or permission flags)
 * and app ops - and vise versa.
 */
public final class PermissionPolicyService extends SystemService {
    private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
    private static final String SYSTEM_PKG = "android";
    private static final boolean DEBUG = false;
    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;

    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private boolean mBootCompleted = false;

    private IAppOpsCallback mAppOpsCallback;

    /** Whether the user is started but not yet stopped */
    @GuardedBy("mLock")
    private final SparseBooleanArray mIsStarted = new SparseBooleanArray();

    /** Callbacks for when a user is initialized */
    @GuardedBy("mLock")
    private OnInitializedCallback mOnInitializedCallback;

    /**
     * Whether an async {@link #synchronizeUidPermissionsAndAppOps} is currently
     * scheduled for a UID.
     */
    @GuardedBy("mLock")
    private final SparseBooleanArray mIsUidSyncScheduled = new SparseBooleanArray();

    /**
     * Whether an async {@link #resetAppOpPermissionsIfNotRequestedForUid} is currently
     * scheduled for a uid.
     */
    @GuardedBy("mLock")
    private final SparseBooleanArray mIsUidResetScheduled = new SparseBooleanArray();

    /**
     * This change reflects the presence of the new Notification Permission
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
    private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L;

    private List<String> mAppOpPermissions;

    private Context mContext;
    private PackageManagerInternal mPackageManagerInternal;
    private PermissionManagerServiceInternal mPermissionManagerInternal;
    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
    private NotificationManagerInternal mNotificationManager;
    private TelephonyManager mTelephonyManager;
    private final KeyguardManager mKeyguardManager;
    private final PackageManager mPackageManager;
    private final Handler mHandler;

    public PermissionPolicyService(@NonNull Context context) {
        super(context);

        mContext = context;
        mHandler = new Handler(Looper.getMainLooper());
        mPackageManager = context.getPackageManager();
        mKeyguardManager = context.getSystemService(KeyguardManager.class);
        LocalServices.addService(PermissionPolicyInternal.class, new Internal());
    }

    @Override
    public void onStart() {
        mPackageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        mPermissionManagerInternal = LocalServices.getService(
                PermissionManagerServiceInternal.class);
        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
                ServiceManager.getService(Context.APP_OPS_SERVICE));

        mPackageManagerInternal.getPackageList(new PackageListObserver() {
            @Override
            public void onPackageAdded(String packageName, int appId) {
                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
                        .getUserIds();
                for (final int userId : userIds) {
                    if (isStarted(userId)) {
                        final int uid = UserHandle.getUid(userId, appId);
                        synchronizeUidPermissionsAndAppOps(uid);
                    }
                }
            }

            @Override
            public void onPackageChanged(String packageName, int appId) {
                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
                        .getUserIds();
                for (final int userId : userIds) {
                    if (isStarted(userId)) {
                        final int uid = UserHandle.getUid(userId, appId);
                        synchronizeUidPermissionsAndAppOps(uid);
                        resetAppOpPermissionsIfNotRequestedForUid(uid);
                    }
                }
            }

            @Override
            public void onPackageRemoved(String packageName, int appId) {
                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
                        .getUserIds();
                for (final int userId : userIds) {
                    if (isStarted(userId)) {
                        final int uid = UserHandle.getUid(userId, appId);
                        resetAppOpPermissionsIfNotRequestedForUid(uid);
                    }
                }
            }
        });

        mPackageManager.addOnPermissionsChangeListener(
                this::synchronizeUidPermissionsAndAppOpsAsync);

        mAppOpsCallback = new IAppOpsCallback.Stub() {
            public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
                if (!Objects.equals(persistentDeviceId,
                        VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) || uid < 0) {
                    return;
                }
                synchronizeUidPermissionsAndAppOpsAsync(uid);
                resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
            }
        };

        final List<PermissionInfo> dangerousPerms =
                mPermissionManagerInternal.getAllPermissionsWithProtection(
                        PermissionInfo.PROTECTION_DANGEROUS);
        try {
            int numDangerousPerms = dangerousPerms.size();
            for (int i = 0; i < numDangerousPerms; i++) {
                PermissionInfo perm = dangerousPerms.get(i);

                if (perm.isRuntime()) {
                    appOpsService.startWatchingMode(getSwitchOp(perm.name), null, mAppOpsCallback);
                }
                if (perm.isSoftRestricted()) {
                    SoftRestrictedPermissionPolicy policy =
                            SoftRestrictedPermissionPolicy.forPermission(null, null, null,
                                    null, perm.name);
                    int extraAppOp = policy.getExtraAppOpCode();
                    if (extraAppOp != OP_NONE) {
                        appOpsService.startWatchingMode(extraAppOp, null, mAppOpsCallback);
                    }
                }
            }
        } catch (RemoteException doesNotHappen) {
            Slog.wtf(LOG_TAG, "Cannot set up app-ops listener");
        }

        final List<PermissionInfo> appOpPermissionInfos =
                mPermissionManagerInternal.getAllPermissionsWithProtectionFlags(
                        PermissionInfo.PROTECTION_FLAG_APPOP);
        mAppOpPermissions = new ArrayList<>();
        final int appOpPermissionInfosSize = appOpPermissionInfos.size();
        for (int i = 0; i < appOpPermissionInfosSize; i++) {
            final PermissionInfo appOpPermissionInfo = appOpPermissionInfos.get(i);

            switch (appOpPermissionInfo.name) {
                case Manifest.permission.ACCESS_NOTIFICATIONS:
                case Manifest.permission.MANAGE_IPSEC_TUNNELS:
                    continue;
                case Manifest.permission.REQUEST_INSTALL_PACKAGES:
                    // Settings allows the user to control the app op if it's not in the default
                    // mode, regardless of whether the app has requested the permission, so we
                    // should not reset it.
                    continue;
                default:
                    final int appOpCode = AppOpsManager.permissionToOpCode(
                            appOpPermissionInfo.name);
                    if (appOpCode != OP_NONE) {
                        mAppOpPermissions.add(appOpPermissionInfo.name);
                        try {
                            appOpsService.startWatchingMode(appOpCode, null, mAppOpsCallback);
                        } catch (RemoteException e) {
                            Slog.wtf(LOG_TAG, "Cannot set up app-ops listener", e);
                        }
                    }
            }
        }

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        intentFilter.addDataScheme("package");

        getContext().registerReceiverAsUser(new BroadcastReceiver() {
            final List<Integer> mUserSetupUids = new ArrayList<>(200);
            final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
                    new HashMap<>();

            @Override
            public void onReceive(Context context, Intent intent) {
                boolean hasSetupRun = true;
                try {
                    final ContentResolver cr = getContext().getContentResolver();
                    hasSetupRun = Settings.Secure.getIntForUser(cr,
                            Settings.Secure.USER_SETUP_COMPLETE, cr.getUserId()) != 0;
                } catch (Settings.SettingNotFoundException e) {
                    // Ignore error, assume setup has run
                }
                int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                // If there is no valid package for the given UID, return immediately
                if (mPackageManagerInternal.getPackage(uid) == null) {
                    return;
                }

                if (hasSetupRun) {
                    if (!mUserSetupUids.isEmpty()) {
                        synchronized (mUserSetupUids) {
                            for (int i = mUserSetupUids.size() - 1; i >= 0; i--) {
                                updateUid(mUserSetupUids.get(i));
                            }
                            mUserSetupUids.clear();
                        }
                    }
                    updateUid(uid);
                } else {
                    synchronized (mUserSetupUids) {
                        if (!mUserSetupUids.contains(uid)) {
                            mUserSetupUids.add(uid);
                        }
                    }
                }
            }

            private void updateUid(int uid) {
                UserHandle user = UserHandle.getUserHandleForUid(uid);
                PermissionControllerManager manager = mPermControllerManagers.get(user);
                if (manager == null) {
                    try {
                        manager = new PermissionControllerManager(
                                getUserContext(getContext(), user), PermissionThread.getHandler());
                    } catch (IllegalStateException exception) {
                        // There's a possible race condition when a user is being removed
                        Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
                                        + user, exception);
                        return;
                    }
                    mPermControllerManagers.put(user, manager);
                }
                manager.updateUserSensitiveForApp(uid);
            }
        }, UserHandle.ALL, intentFilter, null, null);

        PermissionControllerManager manager = new PermissionControllerManager(
                getUserContext(getContext(), Process.myUserHandle()),
                PermissionThread.getHandler());
        PermissionThread.getHandler().postDelayed(manager::updateUserSensitive,
                USER_SENSITIVE_UPDATE_DELAY_MS);
    }

    /**
     * Get op that controls the access related to the permission.
     *
     * <p>Usually the permission-op relationship is 1:1 but some permissions (e.g. fine location)
     * {@link AppOpsManager#opToSwitch(int)}  share an op} to control the access.
     *
     * @param permission The permission
     * @return The op that controls the access of the permission
     */
    private static int getSwitchOp(@NonNull String permission) {
        int op = AppOpsManager.permissionToOpCode(permission);
        if (op == OP_NONE) {
            return OP_NONE;
        }

        return AppOpsManager.opToSwitch(op);
    }

    private void synchronizeUidPermissionsAndAppOpsAsync(int uid) {
        final int userId = UserHandle.getUserId(uid);
        if (isStarted(userId)) {
            synchronized (mLock) {
                if (!mIsUidSyncScheduled.get(uid)) {
                    // TODO(b/165030092): migrate this to PermissionThread.getHandler().
                    // synchronizeUidPermissionsAndAppOps is a heavy operation.
                    // Dispatched on a PermissionThread, it interferes with user switch.
                    // FgThread is busy and schedules it after most of the switch is done.
                    // A possible solution is to delay the callback.
                    FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                            PermissionPolicyService::synchronizeUidPermissionsAndAppOps, this,
                            uid));
                    mIsUidSyncScheduled.put(uid, true);
                } else {
                    if (DEBUG) {
                        Slog.v(LOG_TAG, "sync for UID " + uid + " already scheduled");
                    }
                }
            }
        }
    }

    @Override
    public void onBootPhase(int phase) {
        if (DEBUG) Slog.i(LOG_TAG, "onBootPhase(" + phase + ")");

        if (phase == PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
            registerCarrierPrivilegesCallbacks();
            IntentFilter filter =
                    new IntentFilter(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
            mContext.registerReceiver(mSimConfigBroadcastReceiver, filter);
        }

        if (phase == PHASE_ACTIVITY_MANAGER_READY) {
            final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);

            // For some users we might not receive a onStartUser, hence force one here
            for (int userId : um.getUserIds()) {
                if (um.isUserRunning(userId)) {
                    onStartUser(userId);
                }
            }
        }

        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            ((Internal) LocalServices.getService(PermissionPolicyInternal.class))
                    .onActivityManagerReady();
        }

        if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            synchronized (mLock) {
                mBootCompleted = true;
            }
        }

    }

    private void initTelephonyManagerIfNeeded() {
        if (mTelephonyManager == null) {
            mTelephonyManager = TelephonyManager.from(mContext);
        }
    }

    private void registerCarrierPrivilegesCallbacks() {
        initTelephonyManagerIfNeeded();
        if (mTelephonyManager == null) {
            return;
        }

        int numPhones = mTelephonyManager.getActiveModemCount();
        for (int i = 0; i < numPhones; i++) {
            PhoneCarrierPrivilegesCallback callback = new PhoneCarrierPrivilegesCallback(i);
            mPhoneCarrierPrivilegesCallbacks.add(callback);
            mTelephonyManager.registerCarrierPrivilegesCallback(i, mContext.getMainExecutor(),
                    callback);
        }
    }

    private void unregisterCarrierPrivilegesCallback() {
        initTelephonyManagerIfNeeded();
        if (mTelephonyManager == null) {
            return;
        }

        for (int i = 0; i < mPhoneCarrierPrivilegesCallbacks.size(); i++) {
            PhoneCarrierPrivilegesCallback callback = mPhoneCarrierPrivilegesCallbacks.get(i);
            if (callback != null) {
                mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
            }
        }
        mPhoneCarrierPrivilegesCallbacks.clear();
    }

    private final class PhoneCarrierPrivilegesCallback
            implements TelephonyManager.CarrierPrivilegesCallback {
        private int mPhoneId;

        PhoneCarrierPrivilegesCallback(int phoneId) {
            mPhoneId = phoneId;
        }

        @Override
        public void onCarrierPrivilegesChanged(
                @NonNull Set<String> privilegedPackageNames,
                @NonNull Set<Integer> privilegedUids) {
            initTelephonyManagerIfNeeded();
            if (mTelephonyManager == null) {
                Log.e(LOG_TAG, "Cannot grant default permissions to Carrier Service app. "
                        + "TelephonyManager is null");
                return;
            }

            String servicePkg = mTelephonyManager.getCarrierServicePackageNameForLogicalSlot(
                    mPhoneId);
            if (servicePkg == null) {
                return;
            }
            int[] users = LocalServices.getService(UserManagerInternal.class).getUserIds();
            LegacyPermissionManager legacyPermManager =
                    mContext.getSystemService(LegacyPermissionManager.class);
            for (int i = 0; i < users.length; i++) {
                try {
                    mPackageManager.getPackageInfoAsUser(servicePkg, 0, users[i]);
                    legacyPermManager.grantDefaultPermissionsToCarrierServiceApp(
                            servicePkg, users[i]);
                } catch (PackageManager.NameNotFoundException e) {
                    // Do nothing if the package does not exist for the specified user
                }
            }
        }
    }

    private final ArrayList<PhoneCarrierPrivilegesCallback> mPhoneCarrierPrivilegesCallbacks =
            new ArrayList<>();

    private final BroadcastReceiver mSimConfigBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (!TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED.equals(intent.getAction())) {
                return;
            }
            unregisterCarrierPrivilegesCallback();
            registerCarrierPrivilegesCallbacks();
        }
    };

    /**
     * @return Whether the user is started but not yet stopped
     */
    private boolean isStarted(@UserIdInt int userId) {
        synchronized (mLock) {
            return mIsStarted.get(userId);
        }
    }

    @Override
    public void onUserStarting(@NonNull TargetUser user) {
        onStartUser(user.getUserIdentifier());
    }

    private void onStartUser(@UserIdInt int userId) {
        if (DEBUG) Slog.i(LOG_TAG, "onStartUser(" + userId + ")");

        if (isStarted(userId)) {
            return;
        }


        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("Permission_grant_default_permissions-" + userId);
        if (mPackageManagerInternal.isPermissionUpgradeNeeded(userId)) {
            updateUserSensitive(userId);
            mPackageManagerInternal.updateRuntimePermissionsFingerprint(userId);
        }
        t.traceEnd();

        final OnInitializedCallback callback;

        synchronized (mLock) {
            mIsStarted.put(userId, true);
            callback = mOnInitializedCallback;
        }

        // Force synchronization as permissions might have changed
        t.traceBegin("Permission_synchronize_permissions-" + userId);
        synchronizePermissionsAndAppOpsForUser(userId);
        t.traceEnd();

        // Tell observers we are initialized for this user.
        if (callback != null) {
            t.traceBegin("Permission_onInitialized-" + userId);
            callback.onInitialized(userId);
            t.traceEnd();
        }
    }

    @Override
    public void onUserStopping(@NonNull TargetUser user) {
        if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + user + ")");

        synchronized (mLock) {
            mIsStarted.delete(user.getUserIdentifier());
        }
    }

    private void updateUserSensitive(@UserIdInt int userId) {
        if (DEBUG) Slog.i(LOG_TAG, "updateUserSensitive(" + userId + ")");
        final PermissionControllerManager permissionControllerManager =
                new PermissionControllerManager(
                        getUserContext(getContext(), UserHandle.of(userId)),
                        PermissionThread.getHandler());
        permissionControllerManager.updateUserSensitive();
    }

    private static @Nullable Context getUserContext(@NonNull Context context,
            @Nullable UserHandle user) {
        if (context.getUser().equals(user)) {
            return context;
        } else {
            try {
                return context.createPackageContextAsUser(context.getPackageName(), 0, user);
            } catch (NameNotFoundException e) {
                Slog.e(LOG_TAG, "Cannot create context for user " + user, e);
                return null;
            }
        }
    }

    /**
     * Synchronize a single UID.
     */
    private void synchronizeUidPermissionsAndAppOps(int uid) {
        synchronized (mLock) {
            mIsUidSyncScheduled.delete(uid);
        }

        if (DEBUG) {
            Slog.v(LOG_TAG,
                    "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")");
        }

        final UserHandle user = UserHandle.getUserHandleForUid(uid);
        final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
                getUserContext(getContext(), user));
        final int appId = UserHandle.getAppId(uid);
        final List<AndroidPackage> pkgs = mPackageManagerInternal.getPackagesForAppId(appId);
        final int pkgsSize = pkgs.size();
        for (int i = 0; i < pkgsSize; i++) {
            final AndroidPackage pkg = pkgs.get(i);
            synchroniser.addPackage(pkg.getPackageName());
        }
        synchroniser.syncPackages();
    }

    /**
     * Synchronize all packages
     */
    private void synchronizePermissionsAndAppOpsForUser(@UserIdInt int userId) {
        if (DEBUG) Slog.i(LOG_TAG, "synchronizePermissionsAndAppOpsForUser(" + userId + ")");
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();

        final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
                getUserContext(getContext(), UserHandle.of(userId)));
        t.traceBegin("Permission_synchronize_addPackages-" + userId);
        mPackageManagerInternal.forEachPackage(
                (pkg) -> synchronizer.addPackage(pkg.getPackageName()));
        t.traceEnd();
        t.traceBegin("Permission_syncPackages-" + userId);
        synchronizer.syncPackages();
        t.traceEnd();
    }

    private void resetAppOpPermissionsIfNotRequestedForUidAsync(int uid) {
        if (isStarted(UserHandle.getUserId(uid))) {
            synchronized (mLock) {
                if (!mIsUidResetScheduled.get(uid)) {
                    mIsUidResetScheduled.put(uid, true);
                    PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                            PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid,
                            this, uid));
                }
            }
        }
    }

    private void resetAppOpPermissionsIfNotRequestedForUid(int uid) {
        synchronized (mLock) {
            mIsUidResetScheduled.delete(uid);
        }

        final Context context = getContext();
        final PackageManager userPackageManager = getUserContext(context,
                UserHandle.getUserHandleForUid(uid)).getPackageManager();
        final String[] packageNames = userPackageManager.getPackagesForUid(uid);
        if (packageNames == null || packageNames.length == 0) {
            return;
        }

        final ArraySet<String> requestedPermissions = new ArraySet<>();
        for (String packageName : packageNames) {
            final PackageInfo packageInfo;
            try {
                packageInfo = userPackageManager.getPackageInfo(packageName, GET_PERMISSIONS);
            } catch (NameNotFoundException e) {
                continue;
            }
            if (packageInfo == null || packageInfo.requestedPermissions == null) {
                continue;
            }
            Collections.addAll(requestedPermissions, packageInfo.requestedPermissions);
        }

        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
        final AppOpsManagerInternal appOpsManagerInternal = LocalServices.getService(
                AppOpsManagerInternal.class);
        final int appOpPermissionsSize = mAppOpPermissions.size();
        for (int i = 0; i < appOpPermissionsSize; i++) {
            final String appOpPermission = mAppOpPermissions.get(i);

            if (!requestedPermissions.contains(appOpPermission)) {
                final int appOpCode = AppOpsManager.permissionToOpCode(appOpPermission);
                final int defaultAppOpMode = AppOpsManager.opToDefaultMode(appOpCode);
                for (String packageName : packageNames) {
                    final int appOpMode = appOpsManager.unsafeCheckOpRawNoThrow(appOpCode, uid,
                            packageName);
                    if (appOpMode != defaultAppOpMode) {
                        appOpsManagerInternal.setUidModeFromPermissionPolicy(appOpCode, uid,
                                defaultAppOpMode, mAppOpsCallback);
                        appOpsManagerInternal.setModeFromPermissionPolicy(appOpCode, uid,
                                packageName, defaultAppOpMode, mAppOpsCallback);
                    }
                }
            }
        }
    }

    /**
     * Synchronizes permission to app ops. You *must* always sync all packages
     * in a shared UID at the same time to ensure proper synchronization.
     */
    private class PermissionToOpSynchroniser {
        private final @NonNull Context mContext;
        private final @NonNull PackageManager mPackageManager;
        private final @NonNull AppOpsManager mAppOpsManager;
        private final @NonNull AppOpsManagerInternal mAppOpsManagerInternal;

        private final @NonNull ArrayMap<String, PermissionInfo> mRuntimeAndTheirBgPermissionInfos;

        /**
         * All ops that need to be flipped to allow.
         *
         * @see #syncPackages
         */
        private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();

        /**
         * All ops that need to be flipped to ignore.
         *
         * @see #syncPackages
         */
        private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();

        /**
         * All ops that need to be flipped to ignore if not allowed.
         *
         * Currently, only used by soft restricted permissions logic.
         *
         * @see #syncPackages
         */
        private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();

        /**
         * All ops that need to be flipped to foreground.
         *
         * Currently, only used by the foreground/background permissions logic.
         *
         * @see #syncPackages
         */
        private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>();

        PermissionToOpSynchroniser(@NonNull Context context) {
            mContext = context;
            mPackageManager = context.getPackageManager();
            mAppOpsManager = context.getSystemService(AppOpsManager.class);
            mAppOpsManagerInternal = LocalServices.getService(AppOpsManagerInternal.class);

            mRuntimeAndTheirBgPermissionInfos = new ArrayMap<>();
            PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
                    PermissionManagerServiceInternal.class);
            List<PermissionInfo> permissionInfos =
                    permissionManagerInternal.getAllPermissionsWithProtection(
                            PermissionInfo.PROTECTION_DANGEROUS);
            int permissionInfosSize = permissionInfos.size();
            for (int i = 0; i < permissionInfosSize; i++) {
                PermissionInfo permissionInfo = permissionInfos.get(i);
                mRuntimeAndTheirBgPermissionInfos.put(permissionInfo.name, permissionInfo);
                // Make sure we scoop up all background permissions as they may not be runtime
                if (permissionInfo.backgroundPermission != null) {
                    String backgroundNonRuntimePermission = permissionInfo.backgroundPermission;
                    for (int j = 0; j < permissionInfosSize; j++) {
                        PermissionInfo bgPermissionCandidate = permissionInfos.get(j);
                        if (permissionInfo.backgroundPermission.equals(
                                bgPermissionCandidate.name)) {
                            backgroundNonRuntimePermission = null;
                            break;
                        }
                    }
                    if (backgroundNonRuntimePermission != null) {
                        try {
                            PermissionInfo backgroundPermissionInfo = mPackageManager
                                    .getPermissionInfo(backgroundNonRuntimePermission, 0);
                            mRuntimeAndTheirBgPermissionInfos.put(backgroundPermissionInfo.name,
                                    backgroundPermissionInfo);
                        } catch (NameNotFoundException e) {
                            Slog.w(LOG_TAG, "Unknown background permission: "
                                    + backgroundNonRuntimePermission);
                        }
                    }
                }
            }
        }

        /**
         * Set app ops that were added in {@link #addPackage}.
         *
         * <p>This processes ops previously added by {@link #addPackage(String)})}
         */
        private void syncPackages() {
            // Remember which ops were already set. This makes sure that we always set the most
            // permissive mode if two OpChanges are scheduled. This can e.g. happen if two
            // permissions change the same op. See {@link #getSwitchOp}.
            LongSparseLongArray alreadySetAppOps = new LongSparseLongArray();

            final int allowCount = mOpsToAllow.size();
            for (int i = 0; i < allowCount; i++) {
                final OpToChange op = mOpsToAllow.get(i);

                setUidModeAllowed(op.code, op.uid, op.packageName);
                alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
            }

            final int foregroundCount = mOpsToForeground.size();
            for (int i = 0; i < foregroundCount; i++) {
                final OpToChange op = mOpsToForeground.get(i);
                if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                    continue;
                }

                setUidModeForeground(op.code, op.uid, op.packageName);
                alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
            }

            final int ignoreCount = mOpsToIgnore.size();
            for (int i = 0; i < ignoreCount; i++) {
                final OpToChange op = mOpsToIgnore.get(i);
                if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                    continue;
                }

                setUidModeIgnored(op.code, op.uid, op.packageName);
                alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
            }

            final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
            for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
                final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
                if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                    continue;
                }

                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
                if (wasSet) {
                    alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
                }
            }
        }

        /**
         * Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
         */
        private void addAppOps(@NonNull PackageInfo packageInfo, @NonNull AndroidPackage pkg,
                @NonNull String permissionName) {
            PermissionInfo permissionInfo = mRuntimeAndTheirBgPermissionInfos.get(permissionName);
            if (permissionInfo == null) {
                return;
            }
            addPermissionAppOp(packageInfo, pkg, permissionInfo);
            addExtraAppOp(packageInfo, pkg, permissionInfo);
        }

        private void addPermissionAppOp(@NonNull PackageInfo packageInfo,
                @NonNull AndroidPackage pkg, @NonNull PermissionInfo permissionInfo) {
            if (!permissionInfo.isRuntime()) {
                return;
            }

            String permissionName = permissionInfo.name;
            String packageName = packageInfo.packageName;
            UserHandle user = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
            int permissionFlags = mPackageManager.getPermissionFlags(permissionName,
                    packageName, mContext.getUser());
            boolean isReviewRequired = (permissionFlags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
            if (isReviewRequired) {
                return;
            }

            // TODO: COARSE_LOCATION and FINE_LOCATION shares the same app op. We are solving this
            //  with switch op but once we start syncing single permission this won't work.
            int appOpCode = getSwitchOp(permissionName);
            if (appOpCode == OP_NONE) {
                // Note that background permissions don't have an associated app op.
                return;
            }

            int appOpMode;
            boolean shouldGrantAppOp = shouldGrantAppOp(packageInfo, pkg, permissionInfo);
            if (shouldGrantAppOp) {
                if (permissionInfo.backgroundPermission != null) {
                    PermissionInfo backgroundPermissionInfo = mRuntimeAndTheirBgPermissionInfos.get(
                            permissionInfo.backgroundPermission);
                    boolean shouldGrantBackgroundAppOp = backgroundPermissionInfo != null
                            && shouldGrantAppOp(packageInfo, pkg, backgroundPermissionInfo);
                    appOpMode = shouldGrantBackgroundAppOp ? MODE_ALLOWED : MODE_FOREGROUND;
                } else {
                    appOpMode = MODE_ALLOWED;
                }
            } else {
                appOpMode = MODE_IGNORED;
            }

            int uid = packageInfo.applicationInfo.uid;
            OpToChange opToChange = new OpToChange(uid, packageName, appOpCode);
            switch (appOpMode) {
                case MODE_ALLOWED:
                    mOpsToAllow.add(opToChange);
                    break;
                case MODE_FOREGROUND:
                    mOpsToForeground.add(opToChange);
                    break;
                case MODE_IGNORED:
                    mOpsToIgnore.add(opToChange);
                    break;
            }
        }

        private boolean shouldGrantAppOp(@NonNull PackageInfo packageInfo,
                @NonNull AndroidPackage pkg, @NonNull PermissionInfo permissionInfo) {
            String permissionName = permissionInfo.name;
            String packageName = packageInfo.packageName;
            boolean isGranted = mPackageManager.checkPermission(permissionName, packageName)
                    == PackageManager.PERMISSION_GRANTED;
            if (!isGranted) {
                return false;
            }

            int permissionFlags = mPackageManager.getPermissionFlags(permissionName, packageName,
                    mContext.getUser());
            boolean isRevokedCompat = (permissionFlags & FLAG_PERMISSION_REVOKED_COMPAT)
                    == FLAG_PERMISSION_REVOKED_COMPAT;
            if (isRevokedCompat) {
                return false;
            }

            if (permissionInfo.isHardRestricted()) {
                boolean shouldApplyRestriction =
                        (permissionFlags & FLAG_PERMISSION_APPLY_RESTRICTION)
                                == FLAG_PERMISSION_APPLY_RESTRICTION;
                return !shouldApplyRestriction;
            } else if (permissionInfo.isSoftRestricted()) {
                SoftRestrictedPermissionPolicy policy =
                        SoftRestrictedPermissionPolicy.forPermission(mContext,
                                packageInfo.applicationInfo, pkg, mContext.getUser(),
                                permissionName);
                return policy.mayGrantPermission();
            } else {
                return true;
            }
        }

        private void addExtraAppOp(@NonNull PackageInfo packageInfo, @NonNull AndroidPackage pkg,
                @NonNull PermissionInfo permissionInfo) {
            if (!permissionInfo.isSoftRestricted()) {
                return;
            }

            String permissionName = permissionInfo.name;
            SoftRestrictedPermissionPolicy policy =
                    SoftRestrictedPermissionPolicy.forPermission(mContext,
                            packageInfo.applicationInfo, pkg, mContext.getUser(), permissionName);
            int extraOpCode = policy.getExtraAppOpCode();
            if (extraOpCode == OP_NONE) {
                return;
            }

            int uid = packageInfo.applicationInfo.uid;
            String packageName = packageInfo.packageName;
            OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode);
            if (policy.mayAllowExtraAppOp()) {
                mOpsToAllow.add(extraOpToChange);
            } else {
                if (policy.mayDenyExtraAppOpIfGranted()) {
                    mOpsToIgnore.add(extraOpToChange);
                } else {
                    mOpsToIgnoreIfNotAllowed.add(extraOpToChange);
                }
            }
        }

        /**
         * Add a package for {@link #syncPackages() processing} later.
         *
         * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
         *
         * @param pkgName The package to add for later processing.
         */
        void addPackage(@NonNull String pkgName) {
            final PackageInfo pkgInfo;
            final AndroidPackage pkg;
            try {
                pkgInfo = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
                pkg = mPackageManagerInternal.getPackage(pkgName);
            } catch (NameNotFoundException e) {
                return;
            }

            if (pkgInfo == null || pkg == null || pkgInfo.applicationInfo == null
                    || pkgInfo.requestedPermissions == null) {
                return;
            }

            final int uid = pkgInfo.applicationInfo.uid;
            if (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID) {
                // Root and system server always pass permission checks, so don't touch their app
                // ops to keep compatibility.
                return;
            }

            for (String permission : pkgInfo.requestedPermissions) {
                addAppOps(pkgInfo, pkg, permission);
            }
        }

        private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_ALLOWED, packageName);
        }

        private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
        }

        private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_IGNORED, packageName);
        }

        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
                @NonNull String packageName) {
            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
                    opCode), uid, packageName);
            if (currentMode != MODE_ALLOWED) {
                if (currentMode != MODE_IGNORED) {
                    mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, MODE_IGNORED,
                            mAppOpsCallback);
                }
                return true;
            }
            return false;
        }

        private void setUidMode(int opCode, int uid, int mode,
                @NonNull String packageName) {
            final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
                    opCode), uid, packageName);
            if (oldMode != mode) {
                mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, mode,
                        mAppOpsCallback);
                final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
                        opCode), uid, packageName);
                if (newMode != mode) {
                    // Work around incorrectly-set package mode. It never makes sense for app ops
                    // related to runtime permissions, but can get in the way and we have to reset
                    // it.
                    mAppOpsManagerInternal.setModeFromPermissionPolicy(opCode, uid, packageName,
                            AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback);
                }
            }
        }

        private class OpToChange {
            final int uid;
            final @NonNull String packageName;
            final int code;

            OpToChange(int uid, @NonNull String packageName, int code) {
                this.uid = uid;
                this.packageName = packageName;
                this.code = code;
            }
        }
    }

    private class Internal extends PermissionPolicyInternal {

        private final ActivityInterceptorCallback mActivityInterceptorCallback =
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptorCallback.ActivityInterceptResult
                            onInterceptActivityLaunch(@NonNull ActivityInterceptorInfo info) {
                        return null;
                    }

                    @Override
                    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
                            ActivityInterceptorInfo info) {
                        if (!shouldShowNotificationDialogOrClearFlags(taskInfo,
                                activityInfo.packageName, info.getCallingPackage(),
                                info.getIntent(), info.getCheckedOptions(), activityInfo.name,
                                true)
                                || isNoDisplayActivity(activityInfo, info.getUserId())) {
                            return;
                        }
                        UserHandle user = UserHandle.of(taskInfo.userId);
                        if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
                                activityInfo.packageName, user)) {
                            // Post the activity start checks to ensure the notification channel
                            // checks happen outside the WindowManager global lock.
                            mHandler.post(() -> showNotificationPromptIfNeeded(
                                    activityInfo.packageName, taskInfo.userId, taskInfo.taskId,
                                    info));
                        }
                    }
                };

        private void onActivityManagerReady() {
            mActivityTaskManagerInternal.registerActivityStartInterceptor(
                    ActivityInterceptorCallback.PERMISSION_POLICY_ORDERED_ID,
                    mActivityInterceptorCallback);
        }

        @Override
        public boolean checkStartActivity(@NonNull Intent intent, int callingUid,
                @Nullable String callingPackage) {
            if (callingPackage != null && isActionRemovedForCallingPackage(intent, callingUid,
                    callingPackage)) {
                Slog.w(LOG_TAG, "Action Removed: starting " + intent.toString() + " from "
                        + callingPackage + " (uid=" + callingUid + ")");
                return false;
            }

            if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction())
                    && (callingUid != Process.SYSTEM_UID || !SYSTEM_PKG.equals(callingPackage))) {
                return false;
            }

            return true;
        }

        @Override
        public void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
                int taskId) {
            showNotificationPromptIfNeeded(packageName, userId, taskId, null /* info */);
        }

        void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
                int taskId, @Nullable ActivityInterceptorInfo info) {
            UserHandle user = UserHandle.of(userId);
            if (packageName == null || taskId == ActivityTaskManager.INVALID_TASK_ID
                    || !shouldForceShowNotificationPermissionRequest(packageName, user)) {
                return;
            }

            launchNotificationPermissionRequestDialog(packageName, user, taskId, info);
        }

        @Override
        public boolean isIntentToPermissionDialog(@NonNull Intent intent) {
            return Objects.equals(intent.getPackage(),
                    mPackageManager.getPermissionControllerPackageName())
                    && (Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
                    || Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS));
        }

        @Override
        public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg,
                String callingPkg, Intent intent, String activityName) {
            return shouldShowNotificationDialogOrClearFlags(taskInfo, currPkg, callingPkg, intent,
                    null, activityName, false);
        }

        private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo, int userId) {
            final int themeResource = aInfo.getThemeResource();
            if (themeResource == Resources.ID_NULL) {
                return false;
            }

            return mActivityTaskManagerInternal.isNoDisplay(aInfo.packageName, themeResource,
                    userId);
        }

        /**
         * Determine if a particular task is in the proper state to show a system-triggered
         * permission prompt. A prompt can be shown if the task is just starting, or the task is
         * currently focused, visible, and running, and,
         * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or
         * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or
         * 3. The activity belongs to the same package as the one which launched the task
         * originally, and the task was started with a launcher intent, or
         * 4. The activity is the first activity in a new task, and was started by the app the
         * activity belongs to, and that app has another task that is currently focused, which was
         * started with a launcher intent. This case seeks to identify cases where an app launches,
         * then immediately trampolines to a new activity and task.
         * @param taskInfo The task to be checked
         * @param currPkg The package of the current top visible activity
         * @param callingPkg The package that initiated this dialog action
         * @param intent The intent of the current top visible activity
         * @param options The ActivityOptions of the newly started activity, if this is called due
         *                to an activity start
         * @param startedActivity The ActivityInfo of the newly started activity, if this is called
         *                        due to an activity start
         */
        private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg,
                String callingPkg, Intent intent, ActivityOptions options,
                String topActivityName, boolean startedActivity) {
            if (intent == null || currPkg == null || taskInfo == null || topActivityName == null
                    || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning)
                    && !startedActivity)) {
                return false;
            }
            return isLauncherIntent(intent)
                    || (options != null && options.isEligibleForLegacyPermissionPrompt())
                    || isTaskStartedFromLauncher(currPkg, taskInfo)
                    || (isTaskPotentialTrampoline(topActivityName, currPkg, callingPkg, taskInfo,
                    intent)
                    && (!startedActivity || pkgHasRunningLauncherTask(currPkg, taskInfo)));
        }

        private boolean isTaskPotentialTrampoline(String activityName, String currPkg,
                String callingPkg, TaskInfo taskInfo, Intent intent) {
            return currPkg.equals(callingPkg) && taskInfo.baseIntent.filterEquals(intent)
                    && taskInfo.numActivities == 1
                    && activityName.equals(taskInfo.topActivityInfo.name);
        }

        private boolean pkgHasRunningLauncherTask(String currPkg, TaskInfo taskInfo) {
            ActivityTaskManagerInternal m =
                    LocalServices.getService(ActivityTaskManagerInternal.class);
            try {
                // TODO(b/230616478) Investigate alternatives like ActivityMetricsLaunchObserver
                List<ActivityManager.AppTask> tasks =
                        m.getAppTasks(currPkg, mPackageManager.getPackageUid(currPkg, 0));
                for (int i = 0; i < tasks.size(); i++) {
                    TaskInfo other = tasks.get(i).getTaskInfo();
                    if (other.taskId != taskInfo.taskId && other.isFocused && other.isRunning
                            && isTaskStartedFromLauncher(currPkg, other)) {
                        return true;
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                // Fall through
            }
            return false;
        }

        private boolean isLauncherIntent(Intent intent) {
            return Intent.ACTION_MAIN.equals(intent.getAction())
                    && intent.getCategories() != null
                    && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)
                    || intent.getCategories().contains(Intent.CATEGORY_LEANBACK_LAUNCHER)
                    || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER));
        }

        private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) {
            return taskInfo.baseActivity != null
                    && currPkg.equals(taskInfo.baseActivity.getPackageName())
                    && isLauncherIntent(taskInfo.baseIntent);
        }

        private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user,
                int taskId, @Nullable ActivityInterceptorInfo info) {
            Intent grantPermission = mPackageManager
                    .buildRequestPermissionsIntent(new String[] { POST_NOTIFICATIONS });
            // Prevent the front-most activity entering pip due to overlay activity started on top.
            grantPermission.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_USER_ACTION);
            grantPermission.setAction(
                    ACTION_REQUEST_PERMISSIONS_FOR_OTHER);
            grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName);

            final boolean remoteAnimation = info != null && info.getCheckedOptions() != null
                    && info.getCheckedOptions().getAnimationType() == ANIM_REMOTE_ANIMATION
                    && info.getClearOptionsAnimationRunnable() != null;
            ActivityOptions options = remoteAnimation ? ActivityOptions.makeRemoteAnimation(
                        info.getCheckedOptions().getRemoteAnimationAdapter(),
                        info.getCheckedOptions().getRemoteTransition())
                    : new ActivityOptions(new Bundle());
            options.setTaskOverlay(true, false);
            if (remoteAnimation) {
                // Remote animation set on the intercepted activity will be handled by the grant
                // permission activity, which is launched below. So we need to clear remote
                // animation from the intercepted activity and its siblings to prevent duplication.
                // This should trigger ActivityRecord#clearOptionsAnimationForSiblings for the
                // intercepted activity.
                info.getClearOptionsAnimationRunnable().run();
            }
            try {
                mContext.startActivityAsUser(grantPermission, options.toBundle(), user);
            } catch (Exception e) {
                Log.e(LOG_TAG, "couldn't start grant permission dialog"
                        + "for other package " + pkgName, e);
            }
        }

        @Override
        public boolean isInitialized(int userId) {
            return isStarted(userId);
        }

        @Override
        public void setOnInitializedCallback(@NonNull OnInitializedCallback callback) {
            synchronized (mLock) {
                mOnInitializedCallback = callback;
            }
        }

        /**
         * Check if the intent action is removed for the calling package (often based on target SDK
         * version). If the action is removed, we'll silently cancel the activity launch.
         */
        private boolean isActionRemovedForCallingPackage(@NonNull Intent intent, int callingUid,
                @NonNull String callingPackage) {
            String action = intent.getAction();
            if (action == null) {
                return false;
            }
            switch (action) {
                case TelecomManager.ACTION_CHANGE_DEFAULT_DIALER:
                case Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT: {
                    ApplicationInfo applicationInfo;
                    try {
                        applicationInfo = getContext().getPackageManager().getApplicationInfoAsUser(
                                callingPackage, 0, UserHandle.getUserId(callingUid));
                        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) {
                            // Applications targeting Q or higher should use
                            // RoleManager.createRequestRoleIntent() instead.
                            return true;
                        }
                    } catch (PackageManager.NameNotFoundException e) {
                        Slog.i(LOG_TAG, "Cannot find application info for " + callingPackage);
                    }
                    // Make sure RequestRoleActivity can know the calling package if we allow it.
                    intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
                    return false;
                }
                default:
                    return false;
            }
        }

        private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
                @NonNull UserHandle user) {
            AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
            if (pkg == null || pkg.getPackageName() == null
                    || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
                    || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
                if (pkg == null) {
                    Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
                            + pkgName);
                }
                return false;
            }

            synchronized (mLock) {
                if (!mBootCompleted) {
                    return false;
                }
            }

            if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
                    || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
                    || mKeyguardManager.isKeyguardLocked()) {
                return false;
            }

            int uid = user.getUid(pkg.getUid());
            if (mNotificationManager == null) {
                mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
            }
            boolean hasCreatedNotificationChannels = mNotificationManager
                    .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
            boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS,
                    Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED;
            int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
            boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
            return !granted && hasCreatedNotificationChannels && !explicitlySet;
        }
    }
}
