1 /* 2 * Copyright 2023 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 17 package com.android.server.input; 18 19 import static android.view.InputDevice.SOURCE_MOUSE; 20 import static android.view.InputDevice.SOURCE_ROTARY_ENCODER; 21 import static android.view.MotionEvent.ACTION_SCROLL; 22 import static android.view.MotionEvent.AXIS_HSCROLL; 23 import static android.view.MotionEvent.AXIS_SCROLL; 24 import static android.view.MotionEvent.AXIS_VSCROLL; 25 import static android.view.MotionEvent.AXIS_X; 26 import static android.view.MotionEvent.AXIS_Y; 27 28 import static com.google.common.truth.Truth.assertThat; 29 import static com.google.common.truth.Truth.assertWithMessage; 30 31 import android.os.Binder; 32 import android.view.InputEvent; 33 import android.view.MotionEvent; 34 35 import androidx.test.runner.AndroidJUnit4; 36 37 import org.junit.Before; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.io.FileDescriptor; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.function.BiConsumer; 46 47 /** 48 * Build/Install/Run: 49 * atest InputShellCommandTest 50 */ 51 @RunWith(AndroidJUnit4.class) 52 public class InputShellCommandTest { 53 private TestInputEventInjector mInputEventInjector = new TestInputEventInjector(); 54 55 private InputShellCommand mCommand; 56 57 @Before setUp()58 public void setUp() throws Exception { 59 mCommand = new InputShellCommand(mInputEventInjector); 60 } 61 62 @Test testScroll_withPointerSource_noAxisOption()63 public void testScroll_withPointerSource_noAxisOption() { 64 runCommand("mouse scroll 2 -3"); 65 66 MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); 67 68 assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL); 69 assertAxisValues(event, Map.of(AXIS_X, 2f, AXIS_Y, -3f)); 70 } 71 72 @Test testScroll_withPointerSource_withScrollAxisOptions()73 public void testScroll_withPointerSource_withScrollAxisOptions() { 74 runCommand("mouse scroll 1 -2 --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4"); 75 76 MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); 77 78 assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL); 79 assertAxisValues( 80 event, 81 Map.of( 82 AXIS_X, 1f, 83 AXIS_Y, -2f, 84 AXIS_HSCROLL, 3f, 85 AXIS_VSCROLL, 1.7f, 86 AXIS_SCROLL, -4f)); 87 } 88 89 @Test testScroll_withNonPointerSource_noAxisOption()90 public void testScroll_withNonPointerSource_noAxisOption() { 91 runCommand("rotaryencoder scroll"); 92 93 MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); 94 95 assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); 96 } 97 98 @Test testScroll_withNonPointerSource_withScrollAxisOptions()99 public void testScroll_withNonPointerSource_withScrollAxisOptions() { 100 runCommand("rotaryencoder scroll --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4"); 101 102 MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); 103 104 assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); 105 assertAxisValues(event, Map.of(AXIS_HSCROLL, 3f, AXIS_VSCROLL, 1.7f, AXIS_SCROLL, -4f)); 106 } 107 108 @Test testDefaultScrollSource()109 public void testDefaultScrollSource() { 110 runCommand("scroll --axis SCROLL,-4"); 111 112 MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); 113 114 assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); 115 assertAxisValues(event, Map.of(AXIS_SCROLL, -4f)); 116 } 117 118 @Test testInvalidScrollCommands()119 public void testInvalidScrollCommands() { 120 runCommand("scroll --sdaxis SCROLL,-4"); // invalid option 121 runCommand("scroll --axis MYAXIS,-4"); // invalid axis 122 runCommand("scroll --AXIS SCROLL,-4"); // invalid axis option key 123 runCommand("scroll --axis SCROLL,-4abc"); // invalid axis value 124 125 assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); 126 } 127 128 @Test testInvalidKeyEventCommandArgsCombination()129 public void testInvalidKeyEventCommandArgsCombination() { 130 // --duration and --longpress must not be sent together 131 runCommand("keyevent --duration 1000 --longpress KEYCODE_A"); 132 133 assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); 134 } 135 getSingleInjectedInputEvent()136 private InputEvent getSingleInjectedInputEvent() { 137 assertThat(mInputEventInjector.mInjectedEvents).hasSize(1); 138 return mInputEventInjector.mInjectedEvents.get(0); 139 } 140 assertSourceAndAction(MotionEvent event, int source, int action)141 private void assertSourceAndAction(MotionEvent event, int source, int action) { 142 assertThat(event.getSource()).isEqualTo(source); 143 assertThat(event.getAction()).isEqualTo(action); 144 } 145 assertAxisValues(MotionEvent event, Map<Integer, Float> expectedValues)146 private void assertAxisValues(MotionEvent event, Map<Integer, Float> expectedValues) { 147 for (var entry : expectedValues.entrySet()) { 148 final int axis = entry.getKey(); 149 final float expectedValue = entry.getValue(); 150 final float axisValue = event.getAxisValue(axis); 151 assertWithMessage( 152 String.format( 153 "Expected [%f], found [%f] for axis %s", 154 expectedValue, 155 axisValue, 156 MotionEvent.axisToString(axis))) 157 .that(axisValue).isEqualTo(expectedValue); 158 } 159 } 160 runCommand(String cmd)161 private void runCommand(String cmd) { 162 mCommand.exec( 163 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 164 cmd.split(" ") /* args */); 165 } 166 167 private static class TestInputEventInjector implements BiConsumer<InputEvent, Integer> { 168 List<InputEvent> mInjectedEvents = new ArrayList<>(); 169 170 @Override accept(InputEvent event, Integer injectMode)171 public void accept(InputEvent event, Integer injectMode) { 172 mInjectedEvents.add(event); 173 } 174 } 175 } 176