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 17 package android.car; 18 19 import static android.car.CarProjectionManager.ProjectionAccessPointCallback.ERROR_GENERIC; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.fail; 24 import static org.mockito.ArgumentMatchers.any; 25 import static org.mockito.ArgumentMatchers.anyInt; 26 import static org.mockito.ArgumentMatchers.eq; 27 import static org.mockito.Mockito.inOrder; 28 import static org.mockito.Mockito.mock; 29 import static org.mockito.Mockito.never; 30 import static org.mockito.Mockito.verify; 31 32 import android.car.CarProjectionManager.ProjectionAccessPointCallback; 33 import android.car.testapi.CarProjectionController; 34 import android.car.testapi.FakeCar; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.net.MacAddress; 38 import android.net.wifi.SoftApConfiguration; 39 import android.util.ArraySet; 40 41 import androidx.test.core.app.ApplicationProvider; 42 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.Captor; 49 import org.mockito.InOrder; 50 import org.mockito.Spy; 51 import org.mockito.junit.MockitoJUnit; 52 import org.mockito.junit.MockitoRule; 53 import org.robolectric.RobolectricTestRunner; 54 import org.robolectric.annotation.internal.DoNotInstrument; 55 56 import java.util.Arrays; 57 import java.util.Collections; 58 import java.util.Set; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.Executor; 61 import java.util.concurrent.TimeUnit; 62 63 @RunWith(RobolectricTestRunner.class) 64 @DoNotInstrument 65 public class CarProjectionManagerTest { 66 @Rule 67 public MockitoRule rule = MockitoJUnit.rule(); 68 69 @Captor 70 private ArgumentCaptor<Intent> mIntentArgumentCaptor; 71 72 @Spy 73 private final Context mContext = ApplicationProvider.getApplicationContext(); 74 75 private static final int DEFAULT_TIMEOUT_MS = 1000; 76 77 /** An {@link Executor} that immediately runs its callbacks synchronously. */ 78 private static final Executor DIRECT_EXECUTOR = Runnable::run; 79 80 private CarProjectionManager mProjectionManager; 81 private CarProjectionController mController; 82 private ApCallback mApCallback; 83 84 @Before setUp()85 public void setUp() { 86 FakeCar fakeCar = FakeCar.createFakeCar(mContext); 87 mController = fakeCar.getCarProjectionController(); 88 mProjectionManager = 89 (CarProjectionManager) fakeCar.getCar().getCarManager(Car.PROJECTION_SERVICE); 90 assertThat(mProjectionManager).isNotNull(); 91 92 mApCallback = new ApCallback(); 93 } 94 95 @Test startAp_fail()96 public void startAp_fail() throws InterruptedException { 97 mController.setSoftApConfiguration(null); 98 99 mProjectionManager.startProjectionAccessPoint(mApCallback); 100 mApCallback.mFailed.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 101 assertThat(mApCallback.mFailureReason).isEqualTo(ERROR_GENERIC); 102 } 103 104 @Test startAp_success()105 public void startAp_success() throws InterruptedException { 106 SoftApConfiguration config = new SoftApConfiguration.Builder() 107 .setSsid("Hello") 108 .setBssid(MacAddress.fromString("AA:BB:CC:CC:DD:EE")) 109 .setPassphrase("password", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) 110 .build(); 111 mController.setSoftApConfiguration(config); 112 113 mProjectionManager.startProjectionAccessPoint(mApCallback); 114 mApCallback.mStarted.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 115 assertThat(mApCallback.mSoftApConfiguration).isEqualTo(config); 116 } 117 118 @Test registerProjectionRunner()119 public void registerProjectionRunner() throws CarNotConnectedException { 120 Intent intent = new Intent("my_action"); 121 intent.setPackage("my.package"); 122 mProjectionManager.registerProjectionRunner(intent); 123 124 verify(mContext).bindService(mIntentArgumentCaptor.capture(), any(), 125 eq(Context.BIND_AUTO_CREATE)); 126 assertThat(mIntentArgumentCaptor.getValue()).isEqualTo(intent); 127 } 128 129 @Test keyEventListener_registerMultipleEventListeners()130 public void keyEventListener_registerMultipleEventListeners() { 131 CarProjectionManager.ProjectionKeyEventHandler eventHandler1 = 132 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 133 CarProjectionManager.ProjectionKeyEventHandler eventHandler2 = 134 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 135 136 mProjectionManager.addKeyEventHandler( 137 Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), 138 DIRECT_EXECUTOR, 139 eventHandler1); 140 mProjectionManager.addKeyEventHandler( 141 new ArraySet<>( 142 Arrays.asList( 143 CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP, 144 CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN)), 145 DIRECT_EXECUTOR, 146 eventHandler2); 147 148 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 149 verify(eventHandler1).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 150 verify(eventHandler2).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 151 152 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 153 verify(eventHandler1, never()) 154 .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 155 verify(eventHandler2).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 156 157 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 158 verify(eventHandler1, never()).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 159 verify(eventHandler2, never()).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 160 } 161 162 @Test keyEventHandler_canRegisterAllEvents()163 public void keyEventHandler_canRegisterAllEvents() { 164 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 165 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 166 167 Set<Integer> events = new ArraySet<>(CarProjectionManager.NUM_KEY_EVENTS); 168 for (int evt = 0; evt < CarProjectionManager.NUM_KEY_EVENTS; evt++) { 169 events.add(evt); 170 } 171 172 mProjectionManager.addKeyEventHandler(events, DIRECT_EXECUTOR, eventHandler); 173 174 for (int evt : events) { 175 mController.fireKeyEvent(evt); 176 verify(eventHandler).onKeyEvent(evt); 177 } 178 } 179 180 @Test keyEventHandler_eventsOutOfRange_throw()181 public void keyEventHandler_eventsOutOfRange_throw() { 182 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 183 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 184 185 try { 186 mProjectionManager.addKeyEventHandler(Collections.singleton(-1), eventHandler); 187 fail(); 188 } catch (IllegalArgumentException expected) { } 189 190 try { 191 mProjectionManager.addKeyEventHandler( 192 Collections.singleton(CarProjectionManager.NUM_KEY_EVENTS), eventHandler); 193 fail(); 194 } catch (IllegalArgumentException expected) { } 195 } 196 197 @Test keyEventHandler_whenRegisteredAgain_replacesEventList()198 public void keyEventHandler_whenRegisteredAgain_replacesEventList() { 199 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 200 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 201 InOrder inOrder = inOrder(eventHandler); 202 203 mProjectionManager.addKeyEventHandler( 204 Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), 205 DIRECT_EXECUTOR, 206 eventHandler); 207 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 208 inOrder.verify(eventHandler) 209 .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 210 211 mProjectionManager.addKeyEventHandler( 212 Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN), 213 DIRECT_EXECUTOR, 214 eventHandler); 215 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 216 inOrder.verify(eventHandler, never()) 217 .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 218 } 219 220 @Test keyEventHandler_removed_noLongerFires()221 public void keyEventHandler_removed_noLongerFires() { 222 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 223 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 224 225 mProjectionManager.addKeyEventHandler( 226 Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), 227 DIRECT_EXECUTOR, 228 eventHandler); 229 mProjectionManager.removeKeyEventHandler(eventHandler); 230 231 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 232 verify(eventHandler, never()) 233 .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 234 } 235 236 @Test keyEventHandler_withAlternateExecutor_usesExecutor()237 public void keyEventHandler_withAlternateExecutor_usesExecutor() { 238 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 239 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 240 Executor executor = mock(Executor.class); 241 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 242 243 mProjectionManager.addKeyEventHandler( 244 Collections.singleton( 245 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP), 246 executor, 247 eventHandler); 248 249 mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); 250 verify(eventHandler, never()).onKeyEvent(anyInt()); 251 verify(executor).execute(runnableCaptor.capture()); 252 253 runnableCaptor.getValue().run(); 254 verify(eventHandler) 255 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); 256 } 257 258 private static class ApCallback extends ProjectionAccessPointCallback { 259 CountDownLatch mStarted = new CountDownLatch(1); 260 CountDownLatch mFailed = new CountDownLatch(1); 261 int mFailureReason = -1; 262 SoftApConfiguration mSoftApConfiguration; 263 264 @Override onStarted(SoftApConfiguration softApConfiguration)265 public void onStarted(SoftApConfiguration softApConfiguration) { 266 mSoftApConfiguration = softApConfiguration; 267 mStarted.countDown(); 268 } 269 270 @Override onStopped()271 public void onStopped() { 272 } 273 274 @Override onFailed(int reason)275 public void onFailed(int reason) { 276 mFailureReason = reason; 277 mFailed.countDown(); 278 } 279 } 280 } 281