/*
 * Copyright (C) 2021 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.car.settings.enterprise;

import static com.android.car.settings.common.PreferenceController.AVAILABLE;
import static com.android.car.settings.common.PreferenceController.AVAILABLE_FOR_VIEWING;
import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_PROFILE;
import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG;

import android.Manifest;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.Toast;

import com.android.car.settings.R;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.Logger;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.List;

/**
 * Generic helper methods for this package.
 */
public final class EnterpriseUtils {

    private static final Logger LOG = new Logger(EnterpriseUtils.class);
    // TODO: ideally, we should not create a special user restriction other than what are
    // defined in UserManager.
    public static final String DISABLED_INPUT_METHOD = "disabled-input-method";
    // TODO: same as for DISABLED_INPUT_METHOD
    public static final String BLOCKED_UNINSTALL_APP = "blocked-uninstall-app";

    static final String[] CAMERA_PERMISSIONS = new String[] {
            Manifest.permission.CAMERA
    };
    static final String[] LOCATION_PERMISSIONS = new String[] {
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
    };
    static final String[] MICROPHONE_PERMISSIONS = new String[] {
            Manifest.permission.RECORD_AUDIO
    };

    /**
     * Gets the active admin for the given package.
     */
    @Nullable
    public static ComponentName getAdminWithinPackage(Context context, String packageName) {
        List<ComponentName> admins = context.getSystemService(DevicePolicyManager.class)
                .getActiveAdmins();
        if (admins == null) {
            LOG.v("getAdminWithinPackage(): no active admins on context");
            return null;
        }
        return admins.stream().filter(i -> i.getPackageName().equals(packageName)).findAny()
                .orElse(null);
    }

    /**
     * Gets the active admin info for the given admin .
     */
    @Nullable
    public static DeviceAdminInfo getDeviceAdminInfo(Context context, ComponentName admin) {
        LOG.d("getDeviceAdminInfo()(): " + admin.flattenToShortString());

        ActivityInfo ai;
        try {
            ai = context.getPackageManager().getReceiverInfo(admin, PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            LOG.v("Unable to get activity info for " + admin.flattenToShortString() + ": " + e);
            return null;
        }

        try {
            return new DeviceAdminInfo(context, ai);
        } catch (XmlPullParserException | IOException e) {
            LOG.v("Unable to retrieve device policy for " + admin.flattenToShortString() + ": ",
                    e);
            return null;
        }
    }

    /**
     * Checks whether current user has the flag {@link UserManager.FLAG_DEMO}.
     */
    public static boolean isDemoUser(Context context) {
        return UserManager.isDeviceInDemoMode(context)
                && getUserManager(context).isDemoUser();
    }

    /**
     * Checks whether current user has the flag {@link UserManager.FLAG_ADMIN}.
     */
    public static boolean isAdminUser(Context context) {
        return getUserManager(context).isAdminUser();
    }

    /** Returns the default availability status when a user is restricted by a given restriction */
    public static int getAvailabilityStatusRestricted(Context context, String restriction) {
        if (hasUserRestrictionByUm(context, restriction)) {
            return DISABLED_FOR_PROFILE;
        }
        if (hasUserRestrictionByDpm(context, restriction)) {
            return AVAILABLE_FOR_VIEWING;
        }
        return AVAILABLE;
    }

    /**
     * Checks whether the restriction is set on the current user by device owner / profile owners
     * but not by {@link UserManager}.
     *
     * <p>This includes restriction set on device owner but current user has affiliated profile
     * owner.
     */
    public static boolean hasUserRestrictionByDpm(Context context, String restriction) {
        if (hasUserRestrictionByUm(context, restriction)) {
            return false;
        }
        return getUserManager(context).hasUserRestriction(restriction);
    }

    /**
     * Checks whether there are restrictions set via {@link UserManager} which doesn't include
     * restrictions set by device owner / profile owners.
     */
    public static boolean hasUserRestrictionByUm(Context context, String restriction) {
        return getUserManager(context)
                .hasBaseUserRestriction(restriction, UserHandle.of(context.getUserId()));
    }

    /** Handles onClick() behavior for a disabled preference that has been user restricted */
    public static void onClickWhileDisabled(Context context, FragmentController fragmentController,
            String restriction) {
        if (hasUserRestrictionByDpm(context, restriction)) {
            showActionDisabledByAdminDialog(context, fragmentController, restriction);
        } else {
            showActionUnavailableToast(context);
        }
    }

    private static void showActionDisabledByAdminDialog(Context context,
            FragmentController fragmentController, String restriction) {
        fragmentController.showDialog(
                EnterpriseUtils.getActionDisabledByAdminDialog(context, restriction),
                DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
    }

    private static void showActionUnavailableToast(Context context) {
        Toast.makeText(context, context.getString(R.string.action_unavailable),
                Toast.LENGTH_LONG).show();
        LOG.d(context.getString(R.string.action_unavailable));
    }

    /**
     * Checks whether the device is managed.
     */
    public static boolean isDeviceManaged(Context context) {
        DevicePolicyManager dpm = getDevicePolicyManager(context);
        return dpm.isDeviceManaged();
    }

    /**
     * Checks whether device owner is set on the device.
     */
    public static boolean hasDeviceOwner(Context context) {
        DevicePolicyManager dpm = getDevicePolicyManager(context);
        return dpm.isDeviceManaged() && getDeviceOwner(context) != null;
    }

    /**
     * Gets device owner user id on the device.
     */
    @UserIdInt
    private static int getDeviceOwnerUserId(Context context) {
        return getDevicePolicyManager(context).getDeviceOwnerUserId();
    }

    /**
     * Gets device owner component on the device.
     */
    @Nullable
    private static ComponentName getDeviceOwner(Context context) {
        return getDevicePolicyManager(context).getDeviceOwnerComponentOnAnyUser();
    }

    private static UserManager getUserManager(Context context) {
        return context.getSystemService(UserManager.class);
    }

    private static DevicePolicyManager getDevicePolicyManager(Context context) {
        return context.getSystemService(DevicePolicyManager.class);
    }

    /**
     * Gets an {@code ActionDisabledByAdminDialogFragment} for the target restriction to show on
     * the current user.
     */
    public static ActionDisabledByAdminDialogFragment getActionDisabledByAdminDialog(
            Context context, String restriction) {
        return getActionDisabledByAdminDialog(context, restriction, /* restrictedPackage= */ null);
    }

    /**
     * Gets an {@code ActionDisabledByAdminDialogFragment} when the input method is restricted for
     * the current user.
     */
    public static ActionDisabledByAdminDialogFragment getInputMethodDisabledByAdminDialog(
            Context context, String restriction) {
        return getActionDisabledByAdminDialog(context, restriction, /* restrictedPackage= */ null);
    }

    /**
     * Gets an {@code ActionDisabledByAdminDialogFragment} for the target restriction to show on
     * the current user with additional restricted package information.
     */
    public static ActionDisabledByAdminDialogFragment getActionDisabledByAdminDialog(
            Context context, String restriction, @Nullable String restrictedPackage) {
        int adminUser = hasDeviceOwner(context)
                ? getDeviceOwnerUserId(context)
                : context.getUserId();
        return ActionDisabledByAdminDialogFragment
                .newInstance(restriction, restrictedPackage, adminUser);
    }

    /**
     * Gets enforced admin information from Intent that started the
     * {@code ActionDisabledByAdminDialogActivity}.
     */
    public static EnforcedAdmin getEnforcedAdminFromIntent(Context context, Intent intent) {
        EnforcedAdmin admin = new EnforcedAdmin(null, context.getUser());
        if (intent == null) {
            return admin;
        }
        admin.component = intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN);
        int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, context.getUserId());

        Bundle adminDetails = null;
        if (admin.component == null) {
            DevicePolicyManager devicePolicyManager = getDevicePolicyManager(context);
            admin.component = adminDetails.getParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN);
        }

        if (intent.hasExtra(Intent.EXTRA_USER)) {
            admin.user = intent.getParcelableExtra(Intent.EXTRA_USER);
        } else {
            if (adminDetails != null) {
                userId = adminDetails.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
            }
            if (userId == UserHandle.USER_NULL) {
                admin.user = null;
            } else {
                admin.user = UserHandle.of(userId);
            }
        }
        return admin;
    }

    /**
     * Gets {@code RestrictedLockUtils.EnforcedAdmin} for the device policy that affects
     * current user.
     *
     * @param context for current user
     * @param adminUser which can be either profile owner on current user or device owner on
     *        headless system user
     * @param restriction which can be user restriction or restriction policy defined
     *        in this class
     * @param restrictedPackage is the target package that restriction policy is set
     * @return {@code RestrictedLockUtils.EnforcedAdmin}
     */
    public static EnforcedAdmin getEnforcedAdmin(Context context, @UserIdInt int adminUser,
            @Nullable String restriction, String restrictedPackage) {
        if (restriction == null) {
            return null;
        }
        EnforcedAdmin admin = null;
        if (hasUserRestrictionByDpm(context, restriction)) {
            admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                    context, restriction, context.getUserId());
            LOG.v("getEnforcedAdmin(): " + adminUser + " restriction: " + restriction
                    + " restrictedPackage: " + restrictedPackage);

            if (admin.component == null && context.getUserId() != adminUser) {
                // User restriction might be set on primary user which is user 0 as a device-wide
                // policy.
                admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                        context, restriction, adminUser);
            }
        } else if (restriction.equals(DISABLED_INPUT_METHOD)) {
            if (restrictedPackage == null) {
                LOG.e("getEnforcedAdmin() for " + DISABLED_INPUT_METHOD
                        + " fails since restrictedPackage is null");
                return admin;
            }
            admin = RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
                    context, restrictedPackage, context.getUserId());
        } else if (restriction.equals(BLOCKED_UNINSTALL_APP)) {
            admin = RestrictedLockUtilsInternal.checkIfUninstallBlocked(
                    context, restrictedPackage, context.getUserId());
        }
        LOG.v("getEnforcedAdmin():" + admin);
        return admin;
    }

    private EnterpriseUtils() {
        throw new UnsupportedOperationException("Provides only static methods");
    }
}
