1 /* 2 * Copyright (C) 2019 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.car.hal; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.ArgumentMatchers.anyInt; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.doAnswer; 24 import static org.mockito.Mockito.never; 25 import static org.mockito.Mockito.reset; 26 import static org.mockito.Mockito.verify; 27 import static org.mockito.Mockito.when; 28 29 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction; 30 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 31 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 32 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 33 import android.view.KeyEvent; 34 35 import androidx.test.runner.AndroidJUnit4; 36 37 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder; 38 39 import com.google.common.collect.ImmutableList; 40 import com.google.common.collect.ImmutableSet; 41 42 import org.junit.After; 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.ArgumentCaptor; 48 import org.mockito.Mock; 49 import org.mockito.junit.MockitoJUnit; 50 import org.mockito.junit.MockitoRule; 51 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.List; 55 import java.util.Set; 56 import java.util.function.LongSupplier; 57 58 @RunWith(AndroidJUnit4.class) 59 public class InputHalServiceTest { 60 @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); 61 62 @Mock VehicleHal mVehicleHal; 63 @Mock InputHalService.InputListener mInputListener; 64 @Mock LongSupplier mUptimeSupplier; 65 66 private static final VehiclePropConfig HW_KEY_INPUT_CONFIG = 67 VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build(); 68 private static final int DISPLAY = 42; 69 70 private enum Key { DOWN, UP } 71 72 private InputHalService mInputHalService; 73 74 @Before setUp()75 public void setUp() { 76 when(mUptimeSupplier.getAsLong()).thenReturn(0L); 77 mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier); 78 mInputHalService.init(); 79 } 80 81 @After tearDown()82 public void tearDown() { 83 mInputHalService.release(); 84 mInputHalService = null; 85 } 86 87 @Test ignoresSetListener_beforeKeyInputSupported()88 public void ignoresSetListener_beforeKeyInputSupported() { 89 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 90 91 mInputHalService.setInputListener(mInputListener); 92 93 mInputHalService.handleHalEvents( 94 ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER))); 95 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 96 } 97 98 @Test takesKeyInputProperty()99 public void takesKeyInputProperty() { 100 Set<VehiclePropConfig> offeredProps = ImmutableSet.of( 101 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(), 102 HW_KEY_INPUT_CONFIG, 103 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build()); 104 105 Collection<VehiclePropConfig> takenProps = 106 mInputHalService.takeSupportedProperties(offeredProps); 107 108 assertThat(takenProps).containsExactly(HW_KEY_INPUT_CONFIG); 109 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 110 } 111 112 @Test dispatchesInputEvent_single_toListener()113 public void dispatchesInputEvent_single_toListener() { 114 subscribeListener(); 115 116 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER); 117 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 118 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 119 } 120 121 @Test dispatchesInputEvent_multiple_toListener()122 public void dispatchesInputEvent_multiple_toListener() { 123 subscribeListener(); 124 125 // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here. 126 // We need to make a copy of the information we need at the time of the call. 127 List<KeyEvent> events = new ArrayList<>(); 128 doAnswer(inv -> { 129 KeyEvent event = inv.getArgument(0); 130 events.add(event.copy()); 131 return null; 132 }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY)); 133 134 mInputHalService.handleHalEvents( 135 ImmutableList.of( 136 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER), 137 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU))); 138 139 assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 140 assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 141 142 events.forEach(KeyEvent::recycle); 143 } 144 145 @Test handlesRepeatedKeys()146 public void handlesRepeatedKeys() { 147 subscribeListener(); 148 149 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER); 150 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 151 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 152 assertThat(event.getEventTime()).isEqualTo(0L); 153 assertThat(event.getDownTime()).isEqualTo(0L); 154 assertThat(event.getRepeatCount()).isEqualTo(0); 155 156 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 157 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER); 158 159 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 160 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 161 assertThat(event.getEventTime()).isEqualTo(5L); 162 assertThat(event.getDownTime()).isEqualTo(5L); 163 assertThat(event.getRepeatCount()).isEqualTo(1); 164 165 when(mUptimeSupplier.getAsLong()).thenReturn(10L); 166 event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER); 167 168 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP); 169 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 170 assertThat(event.getEventTime()).isEqualTo(10L); 171 assertThat(event.getDownTime()).isEqualTo(5L); 172 assertThat(event.getRepeatCount()).isEqualTo(0); 173 174 when(mUptimeSupplier.getAsLong()).thenReturn(15L); 175 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER); 176 177 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 178 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 179 assertThat(event.getEventTime()).isEqualTo(15L); 180 assertThat(event.getDownTime()).isEqualTo(15L); 181 assertThat(event.getRepeatCount()).isEqualTo(0); 182 } 183 184 /** 185 * Test for handling rotary knob event. 186 */ 187 @Test handlesRepeatedKeyWithIndents()188 public void handlesRepeatedKeyWithIndents() { 189 subscribeListener(); 190 KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5); 191 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 192 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 193 assertThat(event.getEventTime()).isEqualTo(0L); 194 assertThat(event.getDownTime()).isEqualTo(0L); 195 assertThat(event.getRepeatCount()).isEqualTo(4); 196 197 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 198 event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5); 199 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 200 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 201 assertThat(event.getEventTime()).isEqualTo(5L); 202 assertThat(event.getDownTime()).isEqualTo(5L); 203 assertThat(event.getRepeatCount()).isEqualTo(9); 204 } 205 206 @Test handlesKeyUp_withoutKeyDown()207 public void handlesKeyUp_withoutKeyDown() { 208 subscribeListener(); 209 210 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 211 KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER); 212 213 assertThat(event.getEventTime()).isEqualTo(42L); 214 assertThat(event.getDownTime()).isEqualTo(42L); 215 assertThat(event.getRepeatCount()).isEqualTo(0); 216 } 217 218 @Test separateKeyDownEvents_areIndependent()219 public void separateKeyDownEvents_areIndependent() { 220 subscribeListener(); 221 222 when(mUptimeSupplier.getAsLong()).thenReturn(27L); 223 dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER); 224 225 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 226 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU); 227 228 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 229 assertThat(event.getDownTime()).isEqualTo(42L); 230 assertThat(event.getRepeatCount()).isEqualTo(0); 231 } 232 subscribeListener()233 private void subscribeListener() { 234 mInputHalService.takeSupportedProperties(ImmutableSet.of(HW_KEY_INPUT_CONFIG)); 235 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 236 237 mInputHalService.setInputListener(mInputListener); 238 verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT); 239 } 240 241 makeKeyPropValue(Key action, int code)242 private VehiclePropValue makeKeyPropValue(Key action, int code) { 243 VehiclePropValue v = new VehiclePropValue(); 244 v.prop = VehicleProperty.HW_KEY_INPUT; 245 v.value.int32Values.add( 246 (action == Key.DOWN 247 ? VehicleHwKeyInputAction.ACTION_DOWN 248 : VehicleHwKeyInputAction.ACTION_UP)); 249 v.value.int32Values.add(code); 250 v.value.int32Values.add(DISPLAY); 251 return v; 252 } 253 dispatchSingleEvent(Key action, int code)254 private KeyEvent dispatchSingleEvent(Key action, int code) { 255 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 256 reset(mInputListener); 257 mInputHalService.handleHalEvents(ImmutableList.of(makeKeyPropValue(action, code))); 258 verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY)); 259 reset(mInputListener); 260 return captor.getValue(); 261 } 262 makeKeyPropValueWithIndents(int code, int indents)263 private VehiclePropValue makeKeyPropValueWithIndents(int code, int indents) { 264 VehiclePropValue v = new VehiclePropValue(); 265 v.prop = VehicleProperty.HW_KEY_INPUT; 266 // Only Key.down can have indents. 267 v.value.int32Values.add(VehicleHwKeyInputAction.ACTION_DOWN); 268 v.value.int32Values.add(code); 269 v.value.int32Values.add(DISPLAY); 270 v.value.int32Values.add(indents); 271 return v; 272 } 273 dispatchSingleEventWithIndents(int code, int indents)274 private KeyEvent dispatchSingleEventWithIndents(int code, int indents) { 275 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 276 reset(mInputListener); 277 mInputHalService.handleHalEvents( 278 ImmutableList.of(makeKeyPropValueWithIndents(code, indents))); 279 verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY)); 280 reset(mInputListener); 281 return captor.getValue(); 282 } 283 }