/*
 * 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.power;

import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES;
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;

import static com.android.server.power.AttentionDetector.DEFAULT_POST_DIM_CHECK_DURATION_MILLIS;
import static com.android.server.power.AttentionDetector.DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS;
import static com.android.server.power.AttentionDetector.KEY_POST_DIM_CHECK_DURATION_MILLIS;
import static com.android.server.power.AttentionDetector.KEY_PRE_DIM_CHECK_DURATION_MILLIS;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.attention.AttentionService;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.server.wm.WindowManagerInternal;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@SmallTest
public class AttentionDetectorTest extends AndroidTestCase {
    private static final long DEFAULT_DIM_DURATION_MILLIS = 6_000L;

    @Mock
    private PackageManager mPackageManager;
    @Mock
    private AttentionManagerInternal mAttentionManagerInternal;
    @Mock
    private WindowManagerInternal mWindowManagerInternal;
    @Mock
    private Runnable mOnUserAttention;
    private TestableAttentionDetector mAttentionDetector;
    private AttentionDetector mRealAttentionDetector;
    private long mPreDimCheckDuration;
    private long mNextDimming;
    private int mIsSettingEnabled;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mPackageManager.getAttentionServicePackageName()).thenReturn("com.google.android.as");
        when(mPackageManager.checkPermission(any(), any())).thenReturn(
                PackageManager.PERMISSION_GRANTED);
        when(mAttentionManagerInternal.checkAttention(anyLong(), any()))
                .thenReturn(true);
        when(mWindowManagerInternal.isKeyguardShowingAndNotOccluded()).thenReturn(false);
        mAttentionDetector = new TestableAttentionDetector();
        mRealAttentionDetector = new AttentionDetector(mOnUserAttention, new Object());
        mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE);
        mAttentionDetector.setAttentionServiceSupported(true);
        mNextDimming = SystemClock.uptimeMillis() + 3000L;

        // Save the existing state.
        mIsSettingEnabled = Settings.Secure.getIntForUser(getContext().getContentResolver(),
                Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT);

        Settings.Secure.putIntForUser(getContext().getContentResolver(),
                Settings.Secure.ADAPTIVE_SLEEP, 1, UserHandle.USER_CURRENT);
        mAttentionDetector.updateEnabledFromSettings(getContext());
    }

    @After
    public void tearDown() {
        Settings.Secure.putIntForUser(getContext().getContentResolver(),
                Settings.Secure.ADAPTIVE_SLEEP, mIsSettingEnabled, UserHandle.USER_CURRENT);

        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS,
                Long.toString(DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS), false);
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS,
                Long.toString(DEFAULT_POST_DIM_CHECK_DURATION_MILLIS), false);
    }

    @Test
    public void testOnUserActivity_checksAttention() {
        long when = registerAttention();
        verify(mAttentionManagerInternal).checkAttention(anyLong(), any());
        assertThat(when).isLessThan(mNextDimming);
    }

    @Test
    public void testOnUserActivity_doesntCheckIfNotEnabled() {
        Settings.Secure.putIntForUser(getContext().getContentResolver(),
                Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT);
        mAttentionDetector.updateEnabledFromSettings(getContext());
        long when = registerAttention();
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
        assertThat(mNextDimming).isEqualTo(when);
    }

    @Test
    public void testOnUserActivity_doesntCheckIfNotSupported() {
        mAttentionDetector.setAttentionServiceSupported(false);
        long when = registerAttention();
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
        assertThat(mNextDimming).isEqualTo(when);
    }

    @Test
    public void testOnUserActivity_doesntCheckIfInLockscreen() {
        when(mWindowManagerInternal.isKeyguardShowingAndNotOccluded()).thenReturn(true);

        long when = registerAttention();
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
        assertThat(mNextDimming).isEqualTo(when);
    }

    @Test
    public void testOnUserActivity_doesntCheckIfNotSufficientPermissions() {
        when(mPackageManager.checkPermission(any(), any())).thenReturn(
                PackageManager.PERMISSION_DENIED);

        long when = registerAttention();
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
        assertThat(mNextDimming).isEqualTo(when);
    }

    @Test
    public void testOnUserActivity_disablesSettingIfNotSufficientPermissions() {
        when(mPackageManager.checkPermission(any(), any())).thenReturn(
                PackageManager.PERMISSION_DENIED);

        registerAttention();
        boolean enabled = Settings.Secure.getIntForUser(getContext().getContentResolver(),
                Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT) == 1;
        assertFalse(enabled);
    }

    @Test
    public void testOnUserActivity_doesntCrashIfNoAttentionService() {
        mAttentionManagerInternal = null;
        registerAttention();
        // Does not crash.
    }

    @Test
    public void onUserActivity_ignoresWhiteListedActivityTypes() {
        for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) {
            int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i);
            if (result == -1) {
                throw new AssertionError("User activity " + i + " isn't listed in"
                        + " AttentionDetector#onUserActivity. Please consider how this new activity"
                        + " type affects the attention service.");
            }
        }
    }

    @Test
    public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() {
        long now = SystemClock.uptimeMillis();
        mNextDimming = now;
        mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
        mAttentionDetector.updateUserActivity(mNextDimming + 5000L, DEFAULT_DIM_DURATION_MILLIS);
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
    }

    @Test
    public void testUpdateUserActivity_schedulesTheNextCheck() {
        long now = SystemClock.uptimeMillis();
        mNextDimming = now;
        mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
        long nextTimeout = mAttentionDetector.updateUserActivity(mNextDimming + 5000L,
                DEFAULT_DIM_DURATION_MILLIS);
        assertThat(nextTimeout).isEqualTo(mNextDimming + 5000L);
    }

    @Test
    public void testOnUserActivity_ignoresAfterMaximumExtension() {
        long now = SystemClock.uptimeMillis();
        mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
        mAttentionDetector.updateUserActivity(now + 2000L, DEFAULT_DIM_DURATION_MILLIS);
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
    }

    @Test
    public void testOnUserActivity_ignoresIfAlreadyDoneForThatNextScreenDimming() {
        long when = registerAttention();
        verify(mAttentionManagerInternal).checkAttention(anyLong(), any());
        assertThat(when).isLessThan(mNextDimming);
        clearInvocations(mAttentionManagerInternal);

        long redundantWhen = mAttentionDetector.updateUserActivity(mNextDimming,
                DEFAULT_DIM_DURATION_MILLIS);
        assertThat(redundantWhen).isEqualTo(mNextDimming);
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
    }

    @Test
    public void testOnUserActivity_skipsIfAlreadyScheduled() {
        registerAttention();
        reset(mAttentionManagerInternal);
        long when = mAttentionDetector.updateUserActivity(mNextDimming + 1,
                DEFAULT_DIM_DURATION_MILLIS);
        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
        assertThat(when).isLessThan(mNextDimming);
    }

    @Test
    public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() {
        registerAttention();
        mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP);

        ArgumentCaptor<AttentionCallbackInternal> callbackCaptor = ArgumentCaptor.forClass(
                AttentionCallbackInternal.class);
        verify(mAttentionManagerInternal).cancelAttentionCheck(callbackCaptor.capture());
        assertEquals(callbackCaptor.getValue(), mAttentionDetector.mCallback);
    }

    @Test
    public void testCallbackOnSuccess_ignoresIfNoAttention() {
        registerAttention();
        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_ABSENT,
                SystemClock.uptimeMillis());
        verify(mOnUserAttention, never()).run();
    }

    @Test
    public void testCallbackOnSuccess_callsCallback() {
        registerAttention();
        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis());
        verify(mOnUserAttention).run();
    }

    @Test
    public void testCallbackOnSuccess_doesNotCallNonCurrentCallback() {
        mAttentionDetector.mRequestId = 5;
        registerAttention(); // mRequestId = 6;
        mAttentionDetector.mRequestId = 55;

        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis());
        verify(mOnUserAttention, never()).run();
    }

    @Test
    public void testCallbackOnSuccess_callsCallbackAfterOldCallbackCame() {
        mAttentionDetector.mRequestId = 5;
        registerAttention(); // mRequestId = 6;
        mAttentionDetector.mRequestId = 55;

        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis()); // old callback came
        mAttentionDetector.mRequestId = 6; // now back to current
        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis());
        verify(mOnUserAttention).run();
    }

    @Test
    public void testCallbackOnSuccess_DoesNotGoIntoInfiniteLoop() {
        // Mimic real behavior
        doAnswer((invocation) -> {
            // Mimic a cache hit: calling onSuccess() immediately
            registerAttention();
            mAttentionDetector.mRequestId++;
            mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                    SystemClock.uptimeMillis());
            return null;
        }).when(mOnUserAttention).run();

        registerAttention();
        // This test fails with literal stack overflow:
        // e.g. java.lang.StackOverflowError: stack size 1039KB
        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis());

        // We don't actually get here when the test fails
        verify(mOnUserAttention, atMost(1)).run();
    }

    @Test
    public void testCallbackOnFailure_unregistersCurrentRequestCode() {
        registerAttention();
        mAttentionDetector.mCallback.onFailure(AttentionService.ATTENTION_FAILURE_UNKNOWN);
        mAttentionDetector.mCallback.onSuccess(AttentionService.ATTENTION_SUCCESS_PRESENT,
                SystemClock.uptimeMillis());
        verify(mOnUserAttention, never()).run();
    }

    @Test
    public void testGetPreDimCheckDurationMillis_handlesGoodFlagValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS, "555", false);
        assertThat(mRealAttentionDetector.getPreDimCheckDurationMillis()).isEqualTo(555);
    }

    @Test
    public void testGetPreDimCheckDurationMillis_rejectsNegativeValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS, "-50", false);
        assertThat(mRealAttentionDetector.getPreDimCheckDurationMillis()).isEqualTo(
                DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS);
    }

    @Test
    public void testGetPreDimCheckDurationMillis_rejectsTooBigValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS, "20000", false);
        assertThat(mRealAttentionDetector.getPreDimCheckDurationMillis()).isEqualTo(
                DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS);
    }

    @Test
    public void testGetPreDimCheckDurationMillis_handlesBadFlagValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS, "20000k", false);
        assertThat(mRealAttentionDetector.getPreDimCheckDurationMillis()).isEqualTo(
                DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS);

        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_PRE_DIM_CHECK_DURATION_MILLIS, "0.25", false);
        assertThat(mRealAttentionDetector.getPreDimCheckDurationMillis()).isEqualTo(
                DEFAULT_PRE_DIM_CHECK_DURATION_MILLIS);
    }

    @Test
    public void testGetPostDimCheckDurationMillis_handlesGoodFlagValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "333", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(
                DEFAULT_DIM_DURATION_MILLIS)).isEqualTo(333);
    }

    @Test
    public void testGetPostDimCheckDurationMillis_capsGoodFlagValueByMaxDimDuration() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "7000", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(6500)).isEqualTo(6500);
    }

    @Test
    public void testGetPostDimCheckDurationMillis_rejectsNegativeValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "-50", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(
                DEFAULT_DIM_DURATION_MILLIS)).isEqualTo(
                DEFAULT_POST_DIM_CHECK_DURATION_MILLIS);
    }

    @Test
    public void testGetPostDimCheckDurationMillis_rejectsTooBigValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "20000", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(
                DEFAULT_DIM_DURATION_MILLIS)).isEqualTo(
                DEFAULT_POST_DIM_CHECK_DURATION_MILLIS);
    }

    @Test
    public void testGetPostDimCheckDurationMillis_handlesBadFlagValue() {
        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "20000k", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(
                DEFAULT_DIM_DURATION_MILLIS)).isEqualTo(
                DEFAULT_POST_DIM_CHECK_DURATION_MILLIS);

        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                KEY_POST_DIM_CHECK_DURATION_MILLIS, "0.25", false);
        assertThat(mRealAttentionDetector.getPostDimCheckDurationMillis(
                DEFAULT_DIM_DURATION_MILLIS)).isEqualTo(
                DEFAULT_POST_DIM_CHECK_DURATION_MILLIS);
    }

    private long registerAttention() {
        mPreDimCheckDuration = 4000L;
        mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(),
                PowerManager.USER_ACTIVITY_EVENT_TOUCH);
        return mAttentionDetector.updateUserActivity(mNextDimming, DEFAULT_DIM_DURATION_MILLIS);
    }

    private class TestableAttentionDetector extends AttentionDetector {
        private boolean mAttentionServiceSupported;

        TestableAttentionDetector() {
            super(AttentionDetectorTest.this.mOnUserAttention, new Object());
            mAttentionManager = mAttentionManagerInternal;
            mWindowManager = mWindowManagerInternal;
            mPackageManager = AttentionDetectorTest.this.mPackageManager;
            mContentResolver = getContext().getContentResolver();
            mMaximumExtensionMillis = 10000L;
        }

        void setAttentionServiceSupported(boolean supported) {
            mAttentionServiceSupported = supported;
        }

        @Override
        public boolean isAttentionServiceSupported() {
            return mAttentionServiceSupported;
        }

        @Override
        public long getPreDimCheckDurationMillis() {
            return mPreDimCheckDuration;
        }
    }
}
