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