1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.policy; 17 18 import static android.view.Display.DEFAULT_DISPLAY; 19 import static android.view.KeyEvent.KEYCODE_ALT_LEFT; 20 import static android.view.KeyEvent.KEYCODE_ALT_RIGHT; 21 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; 22 import static android.view.KeyEvent.KEYCODE_CTRL_RIGHT; 23 import static android.view.KeyEvent.KEYCODE_META_LEFT; 24 import static android.view.KeyEvent.KEYCODE_META_RIGHT; 25 import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT; 26 import static android.view.KeyEvent.KEYCODE_SHIFT_RIGHT; 27 import static android.view.KeyEvent.META_ALT_LEFT_ON; 28 import static android.view.KeyEvent.META_ALT_ON; 29 import static android.view.KeyEvent.META_ALT_RIGHT_ON; 30 import static android.view.KeyEvent.META_CTRL_LEFT_ON; 31 import static android.view.KeyEvent.META_CTRL_ON; 32 import static android.view.KeyEvent.META_CTRL_RIGHT_ON; 33 import static android.view.KeyEvent.META_META_LEFT_ON; 34 import static android.view.KeyEvent.META_META_ON; 35 import static android.view.KeyEvent.META_META_RIGHT_ON; 36 import static android.view.KeyEvent.META_SHIFT_LEFT_ON; 37 import static android.view.KeyEvent.META_SHIFT_ON; 38 import static android.view.KeyEvent.META_SHIFT_RIGHT_ON; 39 40 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 41 42 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 43 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; 44 45 import static java.util.Collections.unmodifiableMap; 46 47 import android.content.Context; 48 import android.os.Looper; 49 import android.os.SystemClock; 50 import android.util.ArrayMap; 51 import android.view.InputDevice; 52 import android.view.KeyCharacterMap; 53 import android.view.KeyEvent; 54 import android.view.ViewConfiguration; 55 56 import org.junit.After; 57 import org.junit.Before; 58 59 import java.util.Map; 60 61 class ShortcutKeyTestBase { 62 TestPhoneWindowManager mPhoneWindowManager; 63 final Context mContext = spy(getInstrumentation().getTargetContext()); 64 65 /** Modifier key to meta state */ 66 protected static final Map<Integer, Integer> MODIFIER; 67 static { 68 final Map<Integer, Integer> map = new ArrayMap<>(); map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON)69 map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON); map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON)70 map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON); map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON)71 map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON); map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON)72 map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON); map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON)73 map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON); map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON)74 map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON); map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON)75 map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON); map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON)76 map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON); 77 78 MODIFIER = unmodifiableMap(map); 79 } 80 81 @Before setUp()82 public void setUp() { 83 if (Looper.myLooper() == null) { 84 Looper.prepare(); 85 } 86 87 mPhoneWindowManager = new TestPhoneWindowManager(mContext); 88 } 89 90 @After tearDown()91 public void tearDown() { 92 mPhoneWindowManager.tearDown(); 93 } 94 sendKeyCombination(int[] keyCodes, long duration)95 void sendKeyCombination(int[] keyCodes, long duration) { 96 final long downTime = SystemClock.uptimeMillis(); 97 final int count = keyCodes.length; 98 final KeyEvent[] events = new KeyEvent[count]; 99 int metaState = 0; 100 for (int i = 0; i < count; i++) { 101 final int keyCode = keyCodes[i]; 102 final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 103 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 104 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); 105 event.setDisplayId(DEFAULT_DISPLAY); 106 events[i] = event; 107 // The order is important here, metaState could be updated and applied to the next key. 108 metaState |= MODIFIER.getOrDefault(keyCode, 0); 109 } 110 111 for (KeyEvent event: events) { 112 interceptKey(event); 113 } 114 115 try { 116 Thread.sleep(duration); 117 } catch (InterruptedException e) { 118 throw new RuntimeException(e); 119 } 120 121 for (int i = count - 1; i >= 0; i--) { 122 final long eventTime = SystemClock.uptimeMillis(); 123 final int keyCode = keyCodes[i]; 124 final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, 125 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 126 InputDevice.SOURCE_KEYBOARD); 127 interceptKey(upEvent); 128 metaState &= ~MODIFIER.getOrDefault(keyCode, 0); 129 } 130 } 131 sendKey(int keyCode)132 void sendKey(int keyCode) { 133 sendKey(keyCode, false); 134 } 135 sendKey(int keyCode, boolean longPress)136 void sendKey(int keyCode, boolean longPress) { 137 final long downTime = SystemClock.uptimeMillis(); 138 final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 139 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 140 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); 141 event.setDisplayId(DEFAULT_DISPLAY); 142 interceptKey(event); 143 144 if (longPress) { 145 final long nextDownTime = downTime + ViewConfiguration.getLongPressTimeout(); 146 final KeyEvent nextDownevent = new KeyEvent(downTime, nextDownTime, 147 KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, 0 /*metaState*/, 148 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 149 KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD); 150 interceptKey(nextDownevent); 151 } 152 153 final long eventTime = longPress 154 ? SystemClock.uptimeMillis() + ViewConfiguration.getLongPressTimeout() 155 : SystemClock.uptimeMillis(); 156 final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, 157 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 158 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); 159 interceptKey(upEvent); 160 } 161 interceptKey(KeyEvent keyEvent)162 private void interceptKey(KeyEvent keyEvent) { 163 int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent); 164 if ((actions & ACTION_PASS_TO_USER) != 0) { 165 if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) { 166 mPhoneWindowManager.dispatchUnhandledKey(keyEvent); 167 } 168 } 169 } 170 } 171