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

import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.eventually;

import static com.google.common.truth.Truth.assertWithMessage;

import android.annotation.NonNull;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.car.Car;
import android.car.admin.CarDevicePolicyManager;
import android.car.admin.CreateUserResult;
import android.car.admin.RemoveUserResult;
import android.car.admin.StartUserInBackgroundResult;
import android.car.admin.StopUserResult;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import androidx.test.filters.FlakyTest;

import org.junit.Before;
import org.junit.Test;

public final class CarDevicePolicyManagerTest extends CarMultiUserTestBase {

    private static final String TAG = CarDevicePolicyManagerTest.class.getSimpleName();

    private static final int TIMEOUT_MS = 5_000; // 5 seconds

    private CarDevicePolicyManager mCarDpm;
    private DevicePolicyManager mDpm;
    private KeyguardManager mKeyguardManager;
    private PowerManager mPowerManager;

    @Before
    public void setManager() throws Exception {
        mCarDpm = getCarService(Car.CAR_DEVICE_POLICY_SERVICE);
        Context context = getContext();
        mDpm = context.getSystemService(DevicePolicyManager.class);
        mKeyguardManager = context.getSystemService(KeyguardManager.class);
        mPowerManager = context.getSystemService(PowerManager.class);
    }

    @Test
    public void testRemoveUser() throws Exception {
        assertInitialUserIsAdmin();

        UserInfo user = createUser();
        Log.d(TAG, "removing user " + user.toFullString());
        RemoveUserResult result = mCarDpm.removeUser(user.getUserHandle());
        Log.d(TAG, "result: " + result);

        assertWithMessage("Result of removeUser %s: %s", user.toFullString(), result)
                .that(result.isSuccess()).isTrue();
    }

    @Test
    public void testRemoveUser_whenDisallowed() throws Exception {
        try {
            testRemoveUser();
        } finally {
            mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, false,
                    UserHandle.SYSTEM);
        }
    }

    @Test
    public void testRemoveUser_currentUserSetEphemeral() throws Exception {
        assertInitialUserIsAdmin();
        int initialUserId = getCurrentUserId();

        UserInfo user = createUser();
        Log.d(TAG, "switching to user " + user.toFullString());
        switchUser(user.id);

        Log.d(TAG, "removing user " + user.toFullString());
        RemoveUserResult result = mCarDpm.removeUser(user.getUserHandle());

        assertWithMessage("status of remove user %s (%s)", user.toFullString(), result)
                .that(result.getStatus())
                .isEqualTo(RemoveUserResult.STATUS_SUCCESS_SET_EPHEMERAL);

        assertWithMessage("User %s still exist", user).that(hasUser(user.id)).isTrue();
        assertWithMessage("User %s set as ephemeral", user)
                .that(getUser(user.id).isEphemeral())
                .isTrue();

        // Switch back to the starting user.
        Log.d(TAG, "switching to user " + initialUserId);
        switchUser(initialUserId);

        // User is removed once switch is complete
        Log.d(TAG, "waiting for user to be removed: " + user);
        waitForUserRemoval(user.id);
        waitUntil("User " + user + " exists after switch (should be removed)",
                TIMEOUT_MS, () -> !hasUser(user.id));
    }

    @Test
    public void testCreateUser() throws Exception {
        assertCanAddUser();

        String name = "CarDevicePolicyManagerTest.testCreateUser";
        int type = CarDevicePolicyManager.USER_TYPE_REGULAR;
        Log.d(TAG, "creating new user with name " + name + " and type " + type);
        CreateUserResult result = mCarDpm.createUser(name, type);
        Log.d(TAG, "result: " + result);
        UserHandle user = result.getUserHandle();

        try {
            assertWithMessage("Created user named %s and type %s: %s", name, type,
                    result).that(result.isSuccess()).isTrue();
            assertWithMessage("%s is not an admin user", user).that(
                    mUserManager.isUserAdmin(user.getIdentifier())).isFalse();
            assertWithMessage("Create user with name %s", name).that(
                    mUserManager.getUserInfo(user.getIdentifier()).name).isEqualTo(name);
        } finally {
            if (user != null) {
                removeUser(user.getIdentifier());
            }
        }
    }

    @Test
    public void testCreateAdminUser() throws Exception {
        assertCanAddUser();

        String name = "CarDevicePolicyManagerTest.testCreateUser";
        int type = CarDevicePolicyManager.USER_TYPE_ADMIN;
        Log.d(TAG, "creating new user with name " + name + " and type " + type);
        CreateUserResult result = mCarDpm.createUser(name, type);
        Log.d(TAG, "result: " + result);
        UserHandle user = result.getUserHandle();

        try {
            assertWithMessage("Created user named %s and type %s: %s", name, type,
                    result).that(result.isSuccess()).isTrue();
            assertWithMessage("%s is an admin user", user).that(
                    mUserManager.isUserAdmin(user.getIdentifier())).isTrue();
            assertWithMessage("Create user with name %s", name).that(
                    mUserManager.getUserInfo(user.getIdentifier()).name).isEqualTo(name);

        } finally {
            if (user != null) {
                removeUser(user.getIdentifier());
            }
        }
    }

    @Test
    public void testCreateGuestUser() throws Exception {
        assertCanAddUser();

        String name = "CarDevicePolicyManagerTest.testCreateUser";
        int type = CarDevicePolicyManager.USER_TYPE_GUEST;
        Log.d(TAG, "creating new user with name " + name + " and type " + type);
        CreateUserResult result = mCarDpm.createUser(name, type);
        Log.d(TAG, "result: " + result);
        UserHandle user = result.getUserHandle();

        try {
            assertWithMessage("Created user named %s and type %s: %s", name, type,
                    result).that(result.isSuccess()).isTrue();
            assertWithMessage("%s is a guest user", user).that(
                    mUserManager.isGuestUser(user.getIdentifier())).isTrue();
            assertWithMessage("%s is not an admin user", user).that(
                    mUserManager.isUserAdmin(user.getIdentifier())).isFalse();
            assertWithMessage("Create user with name %s", name).that(
                    mUserManager.getUserInfo(user.getIdentifier()).name).isEqualTo(name);

        } finally {
            if (user != null) {
                removeUser(user.getIdentifier());
            }
        }
    }

    @Test
    public void testStartUserInBackground() throws Exception {
        assertInitialUserIsAdmin();

        UserInfo user = createUser();
        Log.d(TAG, "starting user in background " + user.toFullString());
        StartUserInBackgroundResult result = mCarDpm.startUserInBackground(user.getUserHandle());
        Log.d(TAG, "result: " + result);

        assertWithMessage("Result of startUserInBackground %s: %s", user.toFullString(), result)
                .that(result.isSuccess()).isTrue();
    }

    @Test
    public void testStopUser() throws Exception {
        assertInitialUserIsAdmin();

        UserInfo user = createUser();
        Log.d(TAG, "stopping user in background " + user.toFullString());
        StopUserResult result = mCarDpm.stopUser(user.getUserHandle());
        Log.d(TAG, "result: " + result);

        assertWithMessage("Result of stopUser %s: %s", user.toFullString(), result)
                .that(result.isSuccess()).isTrue();
    }

    @Test
    @FlakyTest(bugId = 396445755)
    public void testLockNow_safe() throws Exception {
        lockNowTest(/* safe= */ true);
    }

    @Test
    @FlakyTest(bugId = 396445755)
    public void testLockNow_unsafe() throws Exception {
        lockNowTest(/* safe= */ false);
    }

    // lockNow() is safe regardless of the UXR state
    private void lockNowTest(boolean safe) throws Exception {

        assertScreenOn();

        runSecureDeviceTest(()-> {
            setDpmSafety(safe);

            try {
                mDpm.lockNow();

                assertLockedEventually();
                assertScreenOn();
            } finally {
                setDpmSafety(/* safe= */ true);
            }
        });
    }

    private void runSecureDeviceTest(@NonNull Runnable test) {
        unlockDevice();
        setUserPin(1234);

        try {
            test.run();
        } finally {
            resetUserPin(1234);
            unlockDevice();
        }
    }

    private void unlockDevice() {
        runShellCommand("input keyevent KEYCODE_POWER");
        runShellCommand("input keyevent KEYCODE_WAKEUP");
        runShellCommand("wm dismiss-keyguard");
        assertUnLockedEventually();
    }

    private void setUserPin(int pin) {
        runShellCommand("locksettings set-pin %d", pin);
    }

    private void resetUserPin(int oldPin) {
        runShellCommand("locksettings clear --old %d", oldPin);
    }

    private void assertUnlocked() {
        assertWithMessage("device is locked").that(mKeyguardManager.isDeviceLocked()).isFalse();
        assertWithMessage("keyguard is locked").that(mKeyguardManager.isKeyguardLocked()).isFalse();
    }

    private void assertUnLockedEventually() {
        eventually(() -> assertUnlocked());
    }

    private void assertLocked() {
        assertDeviceSecure();
        assertWithMessage("device is unlocked").that(mKeyguardManager.isDeviceLocked())
                .isTrue();
        assertWithMessage("keyguard is unlocked").that(mKeyguardManager.isKeyguardLocked())
                .isTrue();
    }

    private void assertLockedEventually() {
        eventually(() -> assertLocked());
    }

    private void assertDeviceSecure() {
        assertWithMessage("device is secure").that(mKeyguardManager.isDeviceSecure()).isTrue();
    }

    private void assertScreenOn() {
        assertWithMessage("screen is off").that(mPowerManager.isInteractive()).isTrue();
    }

    private void setDpmSafety(boolean safe) {
        requireNonUserBuild();
        String state = safe ? "park" : "drive";
        runShellCommand("cmd car_service emulate-driving-state %s", state);
    }
}
