/* * Copyright (C) 2017 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; import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS; import static com.android.server.GestureLauncherService.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.StatusBarManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.telecom.TelecomManager; import android.test.mock.MockContentResolver; import android.testing.TestableLooper; import android.util.MutableBoolean; import android.view.KeyEvent; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; /** * Unit tests for {@link GestureLauncherService}. * runtest frameworks-services -c com.android.server.GestureLauncherServiceTest */ @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class GestureLauncherServiceTest { private static final int FAKE_USER_ID = 1337; private static final int FAKE_SOURCE = 1982; private static final long INITIAL_EVENT_TIME_MILLIS = 20000L; private static final long IGNORED_DOWN_TIME = 1234L; private static final int IGNORED_ACTION = 13; private static final int IGNORED_CODE = 1999; private static final int IGNORED_REPEAT = 42; private static final int IGNORED_META_STATE = 0; private static final int IGNORED_DEVICE_ID = 0; private static final int IGNORED_SCANCODE = 0; private @Mock Context mContext; private @Mock Resources mResources; private @Mock StatusBarManagerInternal mStatusBarManagerInternal; private @Mock TelecomManager mTelecomManager; private @Mock MetricsLogger mMetricsLogger; @Mock private UiEventLogger mUiEventLogger; private MockContentResolver mContentResolver; private GestureLauncherService mGestureLauncherService; @BeforeClass public static void oneTimeInitialization() { if (Looper.myLooper() == null) { Looper.prepare(); } } @Before public void setup() { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); final Context originalContext = InstrumentationRegistry.getContext(); when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); when(mContext.getResources()).thenReturn(mResources); mContentResolver = new MockContentResolver(mContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager); when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent()); mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger, mUiEventLogger); } @Test public void testIsCameraDoubleTapPowerEnabled_configFalse() { withCameraDoubleTapPowerEnableConfigValue(false); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerEnabled(mResources)); } @Test public void testIsCameraDoubleTapPowerEnabled_configTrue() { withCameraDoubleTapPowerEnableConfigValue(true); assertTrue(mGestureLauncherService.isCameraDoubleTapPowerEnabled(mResources)); } @Test public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(0); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(1); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsEmergencyGestureSettingEnabled_settingDisabled() { withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(false); assertFalse(mGestureLauncherService.isEmergencyGestureSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsEmergencyGestureSettingEnabled_settingEnabled() { withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(true); assertTrue(mGestureLauncherService.isEmergencyGestureSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testIsEmergencyGestureSettingEnabled_supportDisabled() { withEmergencyGestureEnabledConfigValue(false); withEmergencyGestureEnabledSettingValue(true); assertFalse(mGestureLauncherService.isEmergencyGestureSettingEnabled( mContext, FAKE_USER_ID)); } @Test public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_enabled() { withEmergencyGesturePowerButtonCooldownPeriodMsValue(4000); assertEquals(4000, mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, FAKE_USER_ID)); } @Test public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_disabled() { withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); assertEquals(0, mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, FAKE_USER_ID)); } @Test public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_cappedAtMaximum() { withEmergencyGesturePowerButtonCooldownPeriodMsValue(10000); assertEquals(GestureLauncherService.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX, mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, FAKE_USER_ID)); } @Test public void testHandleCameraLaunchGesture_userSetupComplete() { withUserSetupCompleteValue(true); boolean useWakeLock = false; assertTrue(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE)); verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(FAKE_SOURCE); } @Test public void testHandleEmergencyGesture_userSetupComplete() { withUserSetupCompleteValue(true); assertTrue(mGestureLauncherService.handleEmergencyGesture()); } @Test public void testHandleCameraLaunchGesture_userSetupNotComplete() { withUserSetupCompleteValue(false); boolean useWakeLock = false; assertFalse(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE)); } @Test public void testHandleEmergencyGesture_userSetupNotComplete() { withUserSetupCompleteValue(false); assertFalse(mGestureLauncherService.handleEmergencyGesture()); } @Test public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS + CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger).histogram("power_consecutive_short_tap_count", 1); verify(mMetricsLogger).histogram("power_double_tap_interval", (int) eventTime); } @Test public void testInterceptPowerKeyDown_firstPowerDown_emergencyGestureNotLaunched() { withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateEmergencyGestureEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS + GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS - 1; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger).histogram("power_double_tap_interval", (int) eventTime); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); // The interval is too long to launch the camera, but short enough to count as a // sequential tap. assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(1, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); withUserSetupCompleteValue(true); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertTrue(outLaunched.value); verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected( StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); verify(mMetricsLogger) .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; // 2nd button triggers camera eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertTrue(outLaunched.value); // Camera checks verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected( StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); verify(mMetricsLogger) .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER); final ArgumentCaptor cameraIntervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), cameraIntervalCaptor.capture()); List cameraIntervals = cameraIntervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, cameraIntervals.get(0).intValue()); assertEquals((int) interval, cameraIntervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); // Continue the button presses for the emergency gesture. // Presses 3 and 4 should not trigger any gesture for (int i = 0; i < 2; i++) { eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertTrue(outLaunched.value); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected(); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(5)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_fiveInboundPresses_emergencyGestureEnabled_launchesFlow() { withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; // 3 more button presses which should not trigger any gesture (camera gesture disabled) for (int i = 0; i < 3; i++) { eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(outLaunched.value); assertTrue(intercepted); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected(); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(5)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_tenInboundPresses_emergencyGestureEnabled_keyIntercepted() { withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; // 3 more button presses which should not trigger any gesture, but intercepts action. for (int i = 0; i < 3; i++) { eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(outLaunched.value); assertTrue(intercepted); // 5 more button presses which should not trigger any gesture, but intercepts action. for (int i = 0; i < 5; i++) { eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } } @Test public void testInterceptPowerKeyDown_triggerEmergency_singleTaps_cooldownTriggered() { // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to reset consecutive tap count long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Subsequent single tap is intercepted, but should not trigger any gesture KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); // Add enough interval to reset consecutive tap count interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Another single tap should be the same (intercepted but should not trigger gesture) keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); interactive = true; outLaunched = new MutableBoolean(true); intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } @Test public void testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() { // Enable camera double tap gesture withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to reset consecutive tap count long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Subsequent double tap is intercepted, but should not trigger any gesture for (int i = 0; i < 2; i++) { KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; } } @Test public void testInterceptPowerKeyDown_triggerEmergency_fiveTaps_cooldownTriggered() { // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to reset consecutive tap count long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Subsequent 5 taps are intercepted, but should not trigger any gesture for (int i = 0; i < 5; i++) { KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; } } @Test public void testInterceptPowerKeyDown_triggerEmergency_fiveFastTaps_gestureIgnored() { // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(/* tapIntervalMs= */ 1); // Add 1 more millisecond and send the event again. eventTime += 1; // Subsequent long press is intercepted, but should not trigger any gesture KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, KeyEvent.FLAG_LONG_PRESS); MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown( keyEvent, /* interactive= */ true, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); } @Test public void testInterceptPowerKeyDown_triggerEmergency_longPress_cooldownTriggered() { // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to reset consecutive tap count long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Subsequent long press is intercepted, but should not trigger any gesture KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, KeyEvent.FLAG_LONG_PRESS); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertTrue(intercepted); assertFalse(outLaunched.value); } @Test public void testInterceptPowerKeyDown_triggerEmergency_cooldownDisabled_cooldownNotTriggered() { // Disable power button cooldown by setting cooldown period to 0 withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to reset consecutive tap count long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Subsequent single tap is NOT intercepted KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); // Add enough interval to reset consecutive tap count interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Long press also NOT intercepted keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, KeyEvent.FLAG_LONG_PRESS); interactive = true; outLaunched = new MutableBoolean(true); intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); } @Test public void testInterceptPowerKeyDown_triggerEmergency_outsideCooldownPeriod_cooldownNotTriggered() { // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(5000); mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); // Trigger emergency by tapping button 5 times long eventTime = triggerEmergencyGesture(); // Add enough interval to be outside of cooldown period long interval = 5001; eventTime += interval; // Subsequent single tap is NOT intercepted KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); // Add enough interval to reset consecutive tap count interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; eventTime += interval; // Long press also NOT intercepted keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, KeyEvent.FLAG_LONG_PRESS); interactive = true; outLaunched = new MutableBoolean(true); intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); } @Test public void testInterceptPowerKeyDown_longpress() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); withUserSetupCompleteValue(true); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, KeyEvent.FLAG_LONG_PRESS); outLaunched.value = false; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(1)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(1)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); withUserSetupCompleteValue(false); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); // The interval is too long to launch the camera, but short enough to count as a // sequential tap. assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); // The interval is too long to launch the camera, but short enough to count as a // sequential tap. assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = true; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(1, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); // The interval is too long to launch the camera, but short enough to count as a // sequential tap. assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() { withCameraDoubleTapPowerEnableConfigValue(false); withCameraDoubleTapPowerDisableSettingValue(1); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(1, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); withUserSetupCompleteValue(true); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertTrue(outLaunched.value); verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected( StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); verify(mMetricsLogger) .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); withUserSetupCompleteValue(false); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); // The interval is too long to launch the camera, but short enough to count as a // sequential tap. assertEquals(2, tapCounts.get(1).intValue()); } @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); boolean interactive = false; MutableBoolean outLaunched = new MutableBoolean(true); boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS; eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = true; intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); assertFalse(intercepted); assertFalse(outLaunched.value); verify(mMetricsLogger, never()) .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); verify(mUiEventLogger, never()).log(any()); final ArgumentCaptor intervalCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_double_tap_interval"), intervalCaptor.capture()); List intervals = intervalCaptor.getAllValues(); assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); assertEquals((int) interval, intervals.get(1).intValue()); final ArgumentCaptor tapCountCaptor = ArgumentCaptor.forClass(Integer.class); verify(mMetricsLogger, times(2)).histogram( eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); List tapCounts = tapCountCaptor.getAllValues(); assertEquals(1, tapCounts.get(0).intValue()); assertEquals(1, tapCounts.get(1).intValue()); } /** * Helper method to trigger emergency gesture by pressing button for 5 times. * * @return last event time. */ private long triggerEmergencyGesture() { return triggerEmergencyGesture(CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1); } /** * Helper method to trigger emergency gesture by pressing button for 5 times with * specified interval between each tap * * @return last event time. */ private long triggerEmergencyGesture(long tapIntervalMs) { // Enable emergency power gesture withEmergencyGestureEnabledConfigValue(true); withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // 4 button presses long eventTime = INITIAL_EVENT_TIME_MILLIS; boolean interactive = true; KeyEvent keyEvent; MutableBoolean outLaunched = new MutableBoolean(false); for (int i = 0; i < 4; i++) { keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); eventTime += tapIntervalMs; } // 5th button press should trigger the emergency flow keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); outLaunched.value = false; boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); long emergencyGestureTapDetectionMinTimeMs = Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS, EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS); assertTrue(intercepted); if (tapIntervalMs * 4 > emergencyGestureTapDetectionMinTimeMs) { assertTrue(outLaunched.value); verify(mUiEventLogger, times(1)) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected(); } else { assertFalse(outLaunched.value); verify(mUiEventLogger, never()) .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); verify(mStatusBarManagerInternal, never()).onEmergencyActionLaunchGestureDetected(); } return eventTime; } private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) { when(mResources.getBoolean( com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled)) .thenReturn(enableConfigValue); } private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) { when(mResources.getBoolean( com.android.internal.R.bool.config_emergencyGestureEnabled)) .thenReturn(enableConfigValue); } private void withCameraDoubleTapPowerDisableSettingValue(int disableSettingValue) { Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, disableSettingValue, UserHandle.USER_CURRENT); } private void withEmergencyGestureEnabledSettingValue(boolean enable) { Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.EMERGENCY_GESTURE_ENABLED, enable ? 1 : 0, UserHandle.USER_CURRENT); } private void withEmergencyGesturePowerButtonCooldownPeriodMsValue(int period) { Settings.Global.putInt( mContentResolver, Settings.Global.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, period); } private void withUserSetupCompleteValue(boolean userSetupComplete) { int userSetupCompleteValue = userSetupComplete ? 1 : 0; Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, userSetupCompleteValue, UserHandle.USER_CURRENT); } }