/*
 * Copyright (C) 2016 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.settingslib;

import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.role.RoleManager.ROLE_FINANCED_DEVICE_KIOSK;

import static com.android.settingslib.Utils.getColorAttrDefaultColor;

import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.EnforcingAdmin;
import android.app.admin.PackagePolicy;
import android.app.admin.UnknownAuthority;
import android.app.ecm.EnhancedConfirmationManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
import android.security.advancedprotection.AdvancedProtectionManager;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

import com.android.internal.widget.LockPatternUtils;

import java.util.HashSet;
import java.util.List;

/**
 * Utility class to host methods usable in adding a restricted padlock icon and showing admin
 * support message dialog.
 */
public class RestrictedLockUtilsInternal extends RestrictedLockUtils {

    private static final String LOG_TAG = "RestrictedLockUtils";
    private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);

    // TODO(b/281701062): reference role name from role manager once its exposed.
    private static final String ROLE_DEVICE_LOCK_CONTROLLER =
            "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";

    //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
    //when the appropriate flag is launched.
    private static final String MEMORY_TAGGING_POLICY = "memoryTagging";

    /**
     * @return drawables for displaying with settings that are locked by a device admin.
     */
    public static Drawable getRestrictedPadlock(Context context) {
        Drawable restrictedPadlock = context.getDrawable(android.R.drawable.ic_info);
        final int iconSize = context.getResources().getDimensionPixelSize(
                android.R.dimen.config_restrictedIconSize);

        TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
        int colorAccent = ta.getColor(0, 0);
        ta.recycle();
        restrictedPadlock.setTint(colorAccent);

        restrictedPadlock.setBounds(0, 0, iconSize, iconSize);
        return restrictedPadlock;
    }

    /**
     * Checks if a given permission requires additional confirmation for the given package
     *
     * @return An intent to show the user if additional confirmation is required, null otherwise
     */
    @Nullable
    public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context,
            @NonNull String settingIdentifier, @NonNull String packageName) {

        if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
                || !android.security.Flags.extendEcmToAllSettings()) {
            return null;
        }

        EnhancedConfirmationManager ecManager = (EnhancedConfirmationManager) context
                .getSystemService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE);
        try {
            if (ecManager.isRestricted(packageName, settingIdentifier)) {
                return ecManager.createRestrictedSettingDialogIntent(
                        packageName, settingIdentifier);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(LOG_TAG, "package not found: " + packageName, e);
        }

        return null;
    }

    /**
     * <p>This is {@code true} when the setting is a protected setting (i.e., a sensitive resource),
     * and the app is restricted (i.e., considered dangerous), and the user has not yet cleared the
     * app's restriction status (i.e., by clicking "Allow restricted settings" for this app).     *
     */
    public static boolean isEnhancedConfirmationRestricted(@NonNull Context context,
            @NonNull String settingIdentifier, @NonNull String packageName) {
        if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
                && android.security.Flags.extendEcmToAllSettings()) {
            try {
                return context.getSystemService(EnhancedConfirmationManager.class)
                        .isRestricted(packageName, settingIdentifier);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
                return false;
            }
        } else {
            try {
                if (!settingIdentifier.equals(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE)) {
                    return false;
                }
                int uid = context.getPackageManager().getPackageUid(packageName, 0);
                final int mode = context.getSystemService(AppOpsManager.class)
                        .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
                        uid, packageName);
                final boolean ecmEnabled = context.getResources().getBoolean(
                        com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
                return ecmEnabled && mode != AppOpsManager.MODE_ALLOWED
                        && mode != AppOpsManager.MODE_DEFAULT;
            } catch (Exception e) {
                // Fallback in case if app ops is not available in testing.
                return false;
            }
        }
    }

    /**
     * Checks if a restriction is enforced on a user and returns the enforced admin and
     * admin userId.
     *
     * @param userRestriction Restriction to check
     * @param userId User which we need to check if restriction is enforced on.
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} If the restriction is not set. If the restriction is set by both device owner
     * and profile owner, then the admin component will be set to {@code null} and userId to
     * {@link UserHandle#USER_NULL}.
     */
    public static EnforcedAdmin checkIfRestrictionEnforced(Context context,
            String userRestriction, int userId) {
        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }

        final UserManager um = UserManager.get(context);
        final UserHandle userHandle = UserHandle.of(userId);
        final List<UserManager.EnforcingUser> enforcingUsers =
                um.getUserRestrictionSources(userRestriction, userHandle);

        if (enforcingUsers.isEmpty()) {
            // Restriction is not enforced.
            return null;
        }
        final int size = enforcingUsers.size();
        if (size > 1) {
            final EnforcedAdmin enforcedAdmin = EnforcedAdmin
                    .createDefaultEnforcedAdminWithRestriction(userRestriction);
            enforcedAdmin.user = userHandle;
            if (DEBUG) {
                Log.d(LOG_TAG, "Multiple (" + size + ") enforcing users for restriction '"
                        + userRestriction + "' on user " + userHandle + "; returning default admin "
                        + "(" + enforcedAdmin + ")");
            }
            return enforcedAdmin;
        }

        final EnforcingUser enforcingUser = enforcingUsers.get(0);
        final int restrictionSource = enforcingUser.getUserRestrictionSource();
        if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
            return null;
        }

        if (android.security.Flags.aapmApi()) {
            EnforcingAdmin admin = dpm.getEnforcingAdmin(userId, userRestriction);
            if (admin != null) {
                return new EnforcedAdmin(admin.getComponentName(), userRestriction,
                        admin.getUserHandle());
            }
        }

        final EnforcedAdmin admin =
                getProfileOrDeviceOwner(context, userRestriction, enforcingUser.getUserHandle());
        if (admin != null) {
            return admin;
        }
        return EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(userRestriction);
    }

    public static boolean hasBaseUserRestriction(Context context,
            String userRestriction, int userId) {
        final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
        return um.hasBaseUserRestriction(userRestriction, UserHandle.of(userId));
    }

    /**
     * Checks whether keyguard features are disabled by policy.
     *
     * @param context {@link Context} for the calling user.
     *
     * @param keyguardFeatures Any one of keyguard features that can be
     * disabled by {@link android.app.admin.DevicePolicyManager#setKeyguardDisabledFeatures}.
     *
     * @param userId User to check enforced admin status for.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} If the notification features are not disabled. If the restriction is set by
     * multiple admins, then the admin component will be set to {@code null} and userId to
     * {@link UserHandle#USER_NULL}.
     */
    public static EnforcedAdmin checkIfKeyguardFeaturesDisabled(Context context,
            int keyguardFeatures, final @UserIdInt int userId) {
        UserInfo userInfo = UserManager.get(context).getUserInfo(userId);
        if (userInfo == null) {
            Log.w(LOG_TAG, "User " + userId + " does not exist");
            return null;
        }

        final LockSettingCheck check =
                (dpm, admin, checkUser) -> {
                    int effectiveFeatures = dpm.getKeyguardDisabledFeatures(admin, checkUser);
                    if (checkUser != userId) {
                        // This case is reached when {@code checkUser} is a managed profile and
                        // {@code userId} is the parent user.
                        effectiveFeatures &= PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
                    }
                    return (effectiveFeatures & keyguardFeatures) != KEYGUARD_DISABLE_FEATURES_NONE;
                };
        if (userInfo.isManagedProfile()) {
            DevicePolicyManager dpm =
                    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
            return findEnforcedAdmin(dpm.getActiveAdminsAsUser(userId), dpm, userId, check);
        }
        return checkForLockSetting(context, userId, check);
    }

    /**
     * @return the UserHandle for a userId. Return null for USER_NULL
     */
    private static UserHandle getUserHandleOf(@UserIdInt int userId) {
        if (userId == UserHandle.USER_NULL) {
            return null;
        } else {
            return UserHandle.of(userId);
        }
    }

    /**
     * Filter a set of device admins based on a predicate {@code check}. This is equivalent to
     * {@code admins.stream().filter(check).map(x → new EnforcedAdmin(admin, userId)} except it's
     * returning a zero/one/many-type thing.
     *
     * @param admins set of candidate device admins identified by {@link ComponentName}.
     * @param userId user to create the resultant {@link EnforcedAdmin} as.
     * @param check filter predicate.
     *
     * @return {@code null} if none of the {@param admins} match.
     *         An {@link EnforcedAdmin} if exactly one of the admins matches.
     *         Otherwise, {@link EnforcedAdmin#MULTIPLE_ENFORCED_ADMIN} for multiple matches.
     */
    @Nullable
    private static EnforcedAdmin findEnforcedAdmin(@Nullable List<ComponentName> admins,
            @NonNull DevicePolicyManager dpm, @UserIdInt int userId,
            @NonNull LockSettingCheck check) {
        if (admins == null) {
            return null;
        }

        final UserHandle user = getUserHandleOf(userId);
        EnforcedAdmin enforcedAdmin = null;
        for (ComponentName admin : admins) {
            if (check.isEnforcing(dpm, admin, userId)) {
                if (enforcedAdmin == null) {
                    enforcedAdmin = new EnforcedAdmin(admin, user);
                } else {
                    return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
                }
            }
        }
        return enforcedAdmin;
    }

    public static EnforcedAdmin checkIfUninstallBlocked(Context context,
            String packageName, int userId) {
        EnforcedAdmin allAppsControlDisallowedAdmin = checkIfRestrictionEnforced(context,
                UserManager.DISALLOW_APPS_CONTROL, userId);
        if (allAppsControlDisallowedAdmin != null) {
            return allAppsControlDisallowedAdmin;
        }
        EnforcedAdmin allAppsUninstallDisallowedAdmin = checkIfRestrictionEnforced(context,
                UserManager.DISALLOW_UNINSTALL_APPS, userId);
        if (allAppsUninstallDisallowedAdmin != null) {
            return allAppsUninstallDisallowedAdmin;
        }
        IPackageManager ipm = AppGlobals.getPackageManager();
        try {
            if (ipm.getBlockUninstallForUser(packageName, userId)) {
                return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
            }
        } catch (RemoteException e) {
            // Nothing to do
        }
        return null;
    }

    /**
     * Check if an application is suspended.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if the application is not suspended.
     */
    public static EnforcedAdmin checkIfApplicationIsSuspended(Context context, String packageName,
            int userId) {
        IPackageManager ipm = AppGlobals.getPackageManager();
        try {
            if (ipm.isPackageSuspendedForUser(packageName, userId)) {
                return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
            }
        } catch (RemoteException | IllegalArgumentException e) {
            // Nothing to do
        }
        return null;
    }

    public static EnforcedAdmin checkIfInputMethodDisallowed(Context context,
            String packageName, int userId) {
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
        boolean permitted = true;
        if (admin != null) {
            permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
                    packageName, userId);
        }

        boolean permittedByParentAdmin = true;
        EnforcedAdmin profileAdmin = null;
        int managedProfileId = getManagedProfileId(context, userId);
        if (managedProfileId != UserHandle.USER_NULL) {
            profileAdmin = getProfileOrDeviceOwner(context, getUserHandleOf(managedProfileId));
            // If the device is an organization-owned device with a managed profile, the
            // managedProfileId will be used instead of the affected userId. This is because
            // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
            // return results affecting the personal profile.
            if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
                DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm,
                        UserManager.get(context).getUserInfo(managedProfileId));
                permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
                        profileAdmin.component, packageName, managedProfileId);
            }
        }
        if (!permitted && !permittedByParentAdmin) {
            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
        } else if (!permitted) {
            return admin;
        } else if (!permittedByParentAdmin) {
            return profileAdmin;
        }
        return null;
    }

    /**
     * @param context
     * @param userId user id of a managed profile.
     * @return is remote contacts search disallowed.
     */
    public static EnforcedAdmin checkIfRemoteContactSearchDisallowed(Context context, int userId) {
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        EnforcedAdmin admin = getProfileOwner(context, userId);
        if (admin == null) {
            return null;
        }
        UserHandle userHandle = UserHandle.of(userId);
        if (dpm.getCrossProfileContactsSearchDisabled(userHandle)
                && dpm.getCrossProfileCallerIdDisabled(userHandle)) {
            return admin;
        }
        return null;
    }

    public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context,
            String packageName, int userId) {
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
        boolean permitted = true;
        if (admin != null) {
            permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
                    packageName, userId);
        }
        int managedProfileId = getManagedProfileId(context, userId);
        EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context,
                getUserHandleOf(managedProfileId));
        boolean permittedByProfileAdmin = true;
        if (profileAdmin != null) {
            permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
                    profileAdmin.component, packageName, managedProfileId);
        }
        if (!permitted && !permittedByProfileAdmin) {
            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
        } else if (!permitted) {
            return admin;
        } else if (!permittedByProfileAdmin) {
            return profileAdmin;
        }
        return null;
    }

    /**
     * Retrieves the user ID of a managed profile associated with a specific user.
     *
     * <p>This method iterates over the users in the profile group associated with the given user ID
     * and returns the ID of the user that is identified as a managed profile user.
     * If no managed profile is found, it returns {@link UserHandle#USER_NULL}.
     *
     * @param context The context used to obtain the {@link UserManager} system service.
     * @param userId  The ID of the user for whom to find the managed profile.
     * @return The user ID of the managed profile, or {@link UserHandle#USER_NULL} if none exists.
     */
    private static int getManagedProfileId(Context context, int userId) {
        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
        List<UserInfo> userProfiles = um.getProfiles(userId);
        for (UserInfo uInfo : userProfiles) {
            if (uInfo.isManagedProfile()) {
                return uInfo.id;
            }
        }
        return UserHandle.USER_NULL;
    }

    /**
     * Check if account management for a specific type of account is disabled by admin.
     * Only a profile or device owner can disable account management. So, we check if account
     * management is disabled and return profile or device owner on the calling user.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if the account management is not disabled.
     */
    public static EnforcedAdmin checkIfAccountManagementDisabled(Context context,
            String accountType, int userId) {
        if (accountType == null) {
            return null;
        }
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        PackageManager pm = context.getPackageManager();
        if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) {
            return null;
        }
        boolean isAccountTypeDisabled = false;
        String[] disabledTypes = dpm.getAccountTypesWithManagementDisabledAsUser(userId);
        for (String type : disabledTypes) {
            if (accountType.equals(type)) {
                isAccountTypeDisabled = true;
                break;
            }
        }
        if (!isAccountTypeDisabled) {
            return null;
        }
        return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
    }

    /**
     * Check if USB data signaling (except from charging functions) is disabled by the admin.
     * Only a device owner or a profile owner on an organization-owned managed profile can disable
     * USB data signaling.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if USB data signaling is not disabled.
     */
    public static EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context, int userId) {
        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
        if (dpm == null || dpm.isUsbDataSignalingEnabled()) {
            return null;
        } else {
            EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
            int managedProfileId = getManagedProfileId(context, userId);
            if (admin == null && managedProfileId != UserHandle.USER_NULL) {
                admin = getProfileOrDeviceOwner(context, getUserHandleOf(managedProfileId));
            }
            return admin;
        }
    }

    /**
     * Check if user control over metered data usage of {@code packageName} is disabled by the
     * profile or device owner.
     *
     * @return EnforcedAdmin object containing the enforced admin component and admin user details,
     * or {@code null} if the user control is not disabled.
     */
    public static EnforcedAdmin checkIfMeteredDataUsageUserControlDisabled(Context context,
            String packageName, int userId) {
        RoleManager roleManager = context.getSystemService(RoleManager.class);
        UserHandle userHandle = getUserHandleOf(userId);
        if (roleManager.getRoleHoldersAsUser(ROLE_FINANCED_DEVICE_KIOSK, userHandle)
                .contains(packageName)
                || roleManager.getRoleHoldersAsUser(ROLE_DEVICE_LOCK_CONTROLLER, userHandle)
                .contains(packageName)) {
            // There is no actual device admin for a financed device, but metered data usage
            // control should still be disabled for both controller and kiosk apps.
            return new EnforcedAdmin();
        }

        final EnforcedAdmin enforcedAdmin = getProfileOrDeviceOwner(context,
                userHandle);
        if (enforcedAdmin == null) {
            return null;
        }

        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        return dpm.isMeteredDataDisabledPackageForUser(enforcedAdmin.component, packageName, userId)
                ? enforcedAdmin : null;
    }

    /**
     * Checks if an admin has enforced minimum password quality or complexity requirements on the
     * given user.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if no quality requirements are set. If the requirements are set by
     * multiple device admins, then the admin component will be set to {@code null} and userId to
     * {@link UserHandle#USER_NULL}.
     */
    public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context, int userId) {
        final LockSettingCheck check =
                (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int checkUser) ->
                        dpm.getPasswordQuality(admin, checkUser)
                                > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;

        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }

        LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
        final int aggregatedComplexity = dpm.getAggregatedPasswordComplexityForUser(userId);
        if (aggregatedComplexity > DevicePolicyManager.PASSWORD_COMPLEXITY_NONE) {
            // First, check if there's a Device Owner. If so, then only it can apply password
            // complexity requiremnts (there can be no secondary profiles).
            final UserHandle deviceOwnerUser = dpm.getDeviceOwnerUser();
            if (deviceOwnerUser != null) {
                return new EnforcedAdmin(dpm.getDeviceOwnerComponentOnAnyUser(), deviceOwnerUser);
            }

            // The complexity could be enforced by a Profile Owner - either in the current user
            // or the current user is the parent user that is affected by the profile owner.
            for (UserInfo userInfo : UserManager.get(context).getProfiles(userId)) {
                final ComponentName profileOwnerComponent = dpm.getProfileOwnerAsUser(userInfo.id);
                if (profileOwnerComponent != null) {
                    return new EnforcedAdmin(profileOwnerComponent, getUserHandleOf(userInfo.id));
                }
            }

            // Should not get here: A Device Owner or Profile Owner should be found.
            throw new IllegalStateException(
                    String.format("Could not find admin enforcing complexity %d for user %d",
                            aggregatedComplexity, userId));
        }

        if (sProxy.isSeparateProfileChallengeEnabled(lockPatternUtils, userId)) {
            // userId is managed profile and has a separate challenge, only consider
            // the admins in that user.
            final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
            if (admins == null) {
                return null;
            }
            EnforcedAdmin enforcedAdmin = null;
            final UserHandle user = getUserHandleOf(userId);
            for (ComponentName admin : admins) {
                if (check.isEnforcing(dpm, admin, userId)) {
                    if (enforcedAdmin == null) {
                        enforcedAdmin = new EnforcedAdmin(admin, user);
                    } else {
                        return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
                    }
                }
            }
            return enforcedAdmin;
        } else {
            return checkForLockSetting(context, userId, check);
        }
    }

    /**
     * Checks if any admin has set maximum time to lock.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if no admin has set this restriction. If multiple admins has set this, then
     * the admin component will be set to {@code null} and userId to {@link UserHandle#USER_NULL}
     */
    public static EnforcedAdmin checkIfMaximumTimeToLockIsSet(Context context) {
        return checkForLockSetting(context, UserHandle.myUserId(),
                (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int userId) ->
                        dpm.getMaximumTimeToLock(admin, userId) > 0);
    }

    private interface LockSettingCheck {
        boolean isEnforcing(DevicePolicyManager dpm, ComponentName admin, @UserIdInt int userId);
    }

    /**
     * Checks whether any of the user's profiles enforce the lock setting. A managed profile is only
     * included if it does not have a separate challenge.
     *
     * The user identified by {@param userId} is always included.
     */
    private static EnforcedAdmin checkForLockSetting(
            Context context, @UserIdInt int userId, LockSettingCheck check) {
        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        final LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
        EnforcedAdmin enforcedAdmin = null;
        // Return all admins for this user and the profiles that are visible from this
        // user that do not use a separate work challenge.
        for (UserInfo userInfo : UserManager.get(context).getProfiles(userId)) {
            final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
            if (admins == null) {
                continue;
            }
            final UserHandle user = getUserHandleOf(userInfo.id);
            final boolean isSeparateProfileChallengeEnabled =
                    sProxy.isSeparateProfileChallengeEnabled(lockPatternUtils, userInfo.id);
            for (ComponentName admin : admins) {
                if (!isSeparateProfileChallengeEnabled) {
                    if (check.isEnforcing(dpm, admin, userInfo.id)) {
                        if (enforcedAdmin == null) {
                            enforcedAdmin = new EnforcedAdmin(admin, user);
                        } else {
                            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
                        }
                        // This same admins could have set policies both on the managed profile
                        // and on the parent. So, if the admin has set the policy on the
                        // managed profile here, we don't need to further check if that admin
                        // has set policy on the parent admin.
                        continue;
                    }
                }
                if (userInfo.isManagedProfile()) {
                    // If userInfo.id is a managed profile, we also need to look at
                    // the policies set on the parent.
                    DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm, userInfo);
                    if (check.isEnforcing(parentDpm, admin, userInfo.id)) {
                        if (enforcedAdmin == null) {
                            enforcedAdmin = new EnforcedAdmin(admin, user);
                        } else {
                            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
                        }
                    }
                }
            }
        }
        return enforcedAdmin;
    }

    public static EnforcedAdmin getDeviceOwner(Context context) {
        return getDeviceOwner(context, null);
    }

    private static EnforcedAdmin getDeviceOwner(Context context, String enforcedRestriction) {
        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        ComponentName adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();
        if (adminComponent != null) {
            return new EnforcedAdmin(
                    adminComponent, enforcedRestriction, dpm.getDeviceOwnerUser());
        }
        return null;
    }

    private static EnforcedAdmin getProfileOwner(Context context, int userId) {
        return getProfileOwner(context, null, userId);
    }

    private static EnforcedAdmin getProfileOwner(
            Context context, String enforcedRestriction, int userId) {
        if (userId == UserHandle.USER_NULL) {
            return null;
        }
        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm == null) {
            return null;
        }
        ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId);
        if (adminComponent != null) {
            return new EnforcedAdmin(adminComponent, enforcedRestriction, getUserHandleOf(userId));
        }
        return null;
    }

    /**
     * Set the menu item as disabled by admin by adding a restricted padlock at the end of the
     * text and set the click listener which will send an intent to show the admin support details
     * dialog. If the admin is null, remove the padlock and disabled color span. When the admin is
     * null, we also set the OnMenuItemClickListener to null, so if you want to set a custom
     * OnMenuItemClickListener, set it after calling this method.
     */
    public static void setMenuItemAsDisabledByAdmin(final Context context,
            final MenuItem item, final EnforcedAdmin admin) {
        SpannableStringBuilder sb = new SpannableStringBuilder(item.getTitle());
        removeExistingRestrictedSpans(sb);

        if (admin != null) {
            final int disabledColor = getColorAttrDefaultColor(context,
                    android.R.attr.textColorHint);
            sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(),
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ImageSpan image = new RestrictedLockImageSpan(context);
            sb.append(" ", image, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    sendShowAdminSupportDetailsIntent(context, admin);
                    return true;
                }
            });
        } else {
            item.setOnMenuItemClickListener(null);
        }
        item.setTitle(sb);
    }

    private static void removeExistingRestrictedSpans(SpannableStringBuilder sb) {
        final int length = sb.length();
        RestrictedLockImageSpan[] imageSpans = sb.getSpans(length - 1, length,
                RestrictedLockImageSpan.class);
        for (ImageSpan span : imageSpans) {
            final int start = sb.getSpanStart(span);
            final int end = sb.getSpanEnd(span);
            sb.removeSpan(span);
            sb.delete(start, end);
        }
        ForegroundColorSpan[] colorSpans = sb.getSpans(0, length, ForegroundColorSpan.class);
        for (ForegroundColorSpan span : colorSpans) {
            sb.removeSpan(span);
        }
    }

    public static boolean isAdminInCurrentUserOrProfile(Context context, ComponentName admin) {
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        UserManager um = UserManager.get(context);
        for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) {
            if (dpm.isAdminActiveAsUser(admin, userInfo.id)) {
                return true;
            }
        }
        return false;
    }

    public static void setTextViewPadlock(Context context,
            TextView textView, boolean showPadlock) {
        final SpannableStringBuilder sb = new SpannableStringBuilder(textView.getText());
        removeExistingRestrictedSpans(sb);
        if (showPadlock) {
            final ImageSpan image = new RestrictedLockImageSpan(context);
            sb.append(" ", image, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        textView.setText(sb);
    }

    /**
     * Takes a {@link android.widget.TextView} and applies an alpha so that the text looks like
     * disabled and appends a padlock to the text. This assumes that there are no
     * ForegroundColorSpans and RestrictedLockImageSpans used on the TextView.
     */
    public static void setTextViewAsDisabledByAdmin(Context context,
            TextView textView, boolean disabled) {
        final SpannableStringBuilder sb = new SpannableStringBuilder(textView.getText());
        removeExistingRestrictedSpans(sb);
        if (disabled) {
            final int disabledColor = Utils.getDisabled(context,
                    textView.getCurrentTextColor());
            sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(),
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            textView.setCompoundDrawables(null, null, getRestrictedPadlock(context), null);
            textView.setCompoundDrawablePadding(context.getResources().getDimensionPixelSize(
                    R.dimen.restricted_icon_padding));
        } else {
            textView.setCompoundDrawables(null, null, null, null);
        }
        textView.setText(sb);
    }

    /**
     * Checks whether MTE (Advanced memory protection) controls are disabled by the enterprise
     * policy.
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public static EnforcedAdmin checkIfMteIsDisabled(Context context) {
        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
        if (dpm.getMtePolicy() == MTE_NOT_CONTROLLED_BY_POLICY) {
            return null;
        }
        EnforcingAdmin enforcingAdmin = context.getSystemService(DevicePolicyManager.class)
                .getEnforcingAdmin(context.getUserId(), MEMORY_TAGGING_POLICY);
        if (enforcingAdmin == null) {
            Log.w(LOG_TAG, "MTE is controlled by policy but could not find enforcing admin.");
        }

        return EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(MEMORY_TAGGING_POLICY);
    }

    /**
     * Checks if the identifier is enforced by advanced protection.
     */
    @RequiresApi(Build.VERSION_CODES.BAKLAVA)
    public static boolean isPolicyEnforcedByAdvancedProtection(Context context, String identifier,
            int userId) {
        if (!android.security.Flags.aapmApi()) return false;
        if (identifier == null) return false;
        EnforcingAdmin admin = context.getSystemService(DevicePolicyManager.class)
                .getEnforcingAdmin(userId, identifier);
        if (admin == null) return false;
        return admin.getAuthority() instanceof UnknownAuthority authority
                && AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY.equals(
                        authority.getName());
    }

    /**
     * Check if there are restrictions on an application from being a Credential Manager provider.
     *
     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
     * or {@code null} if the setting is not managed.
     */
    public static @Nullable EnforcedAdmin checkIfApplicationCanBeCredentialManagerProvider(
            @NonNull Context context, @NonNull String packageName) {
        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
        final PackagePolicy pp = dpm.getCredentialManagerPolicy();

        if (pp == null || pp.isPackageAllowed(packageName, new HashSet<>())) {
            return null;
        }

        EnforcedAdmin admin = RestrictedLockUtilsInternal.getDeviceOwner(context);
        if (admin != null) {
            return admin;
        }
        int profileId = getManagedProfileId(context, context.getUserId());
        return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId));
    }

    /**
     * Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
     * {@link LockPatternUtils} is an internal API not supported by robolectric.
     * {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric.
     */
    @VisibleForTesting
    static Proxy sProxy = new Proxy();

    @VisibleForTesting
    static class Proxy {
        public boolean isSeparateProfileChallengeEnabled(LockPatternUtils utils, int userHandle) {
            return utils.isSeparateProfileChallengeEnabled(userHandle);
        }

        public DevicePolicyManager getParentProfileInstance(DevicePolicyManager dpm, UserInfo ui) {
            return dpm.getParentProfileInstance(ui);
        }
    }
}
