/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car; import static android.car.CarProjectionManager.ProjectionAccessPointCallback.ERROR_GENERIC; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; import android.car.CarProjectionManager.ProjectionAccessPointCallback; import android.car.testapi.CarProjectionController; import android.car.testapi.FakeCar; import android.content.Context; import android.content.Intent; import android.net.MacAddress; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiConfiguration; import android.os.Looper; import android.util.ArraySet; import androidx.test.core.app.ApplicationProvider; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.internal.DoNotInstrument; import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @DoNotInstrument public class CarProjectionManagerTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); @Captor private ArgumentCaptor mIntentArgumentCaptor; @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); private static final int DEFAULT_TIMEOUT_MS = 1000; /** An {@link Executor} that immediately runs its callbacks synchronously. */ private static final Executor DIRECT_EXECUTOR = Runnable::run; private CarProjectionManager mProjectionManager; private CarProjectionController mController; private ApCallback mApCallback; @Before public void setUp() { FakeCar fakeCar = FakeCar.createFakeCar(mContext); mController = fakeCar.getCarProjectionController(); mProjectionManager = (CarProjectionManager) fakeCar.getCar().getCarManager(Car.PROJECTION_SERVICE); assertThat(mProjectionManager).isNotNull(); mApCallback = new ApCallback(); } @Test public void startAp_fail() throws InterruptedException { mController.setSoftApConfiguration(null); mProjectionManager.startProjectionAccessPoint(mApCallback); shadowOf(Looper.getMainLooper()).idle(); mApCallback.mFailed.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); assertThat(mApCallback.mFailureReason).isEqualTo(ERROR_GENERIC); } @Test public void startAp_success() throws InterruptedException { SoftApConfiguration config = new SoftApConfiguration.Builder() .setSsid("Hello") .setBssid(MacAddress.fromString("AA:BB:CC:CC:DD:EE")) .setPassphrase("password", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE) .build(); mController.setSoftApConfiguration(config); mProjectionManager.startProjectionAccessPoint(mApCallback); shadowOf(Looper.getMainLooper()).idle(); mApCallback.mStarted.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); assertThat(mApCallback.mSoftApConfiguration).isEqualTo(config); } @Test public void startAp_success_setWifiConfiguration() throws InterruptedException { SoftApConfiguration config = new SoftApConfiguration.Builder() .setSsid("Hello") .setBssid(MacAddress.fromString("AA:BB:CC:CC:DD:EE")) .setPassphrase("password", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE) .build(); WifiConfiguration wifiConfig = config.toWifiConfiguration(); mController.setWifiConfiguration(wifiConfig); mProjectionManager.startProjectionAccessPoint(mApCallback); shadowOf(Looper.getMainLooper()).idle(); mApCallback.mStarted.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); assertThat(mApCallback.mSoftApConfiguration).isNull(); assertThat(mApCallback.mWifiConfiguration).isEqualTo(wifiConfig); } @Test public void registerProjectionRunner() throws CarNotConnectedException { Intent intent = new Intent("my_action"); intent.setPackage("my.package"); mProjectionManager.registerProjectionRunner(intent); verify(mContext).bindService(mIntentArgumentCaptor.capture(), any(), eq(Context.BIND_AUTO_CREATE)); assertThat(mIntentArgumentCaptor.getValue()).isEqualTo(intent); } @Test public void keyEventListener_registerMultipleEventListeners() { CarProjectionManager.ProjectionKeyEventHandler eventHandler1 = mock(CarProjectionManager.ProjectionKeyEventHandler.class); CarProjectionManager.ProjectionKeyEventHandler eventHandler2 = mock(CarProjectionManager.ProjectionKeyEventHandler.class); mProjectionManager.addKeyEventHandler( Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), DIRECT_EXECUTOR, eventHandler1); mProjectionManager.addKeyEventHandler( new ArraySet<>( Arrays.asList( CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP, CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN)), DIRECT_EXECUTOR, eventHandler2); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); verify(eventHandler1).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); verify(eventHandler2).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); verify(eventHandler1, never()) .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); verify(eventHandler2).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); verify(eventHandler1, never()).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); verify(eventHandler2, never()).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); } @Test public void keyEventHandler_canRegisterAllEvents() { CarProjectionManager.ProjectionKeyEventHandler eventHandler = mock(CarProjectionManager.ProjectionKeyEventHandler.class); Set events = new ArraySet<>(CarProjectionManager.NUM_KEY_EVENTS); for (int evt = 0; evt < CarProjectionManager.NUM_KEY_EVENTS; evt++) { events.add(evt); } mProjectionManager.addKeyEventHandler(events, DIRECT_EXECUTOR, eventHandler); for (int evt : events) { mController.fireKeyEvent(evt); verify(eventHandler).onKeyEvent(evt); } } @Test public void keyEventHandler_eventsOutOfRange_throw() { CarProjectionManager.ProjectionKeyEventHandler eventHandler = mock(CarProjectionManager.ProjectionKeyEventHandler.class); try { mProjectionManager.addKeyEventHandler(Collections.singleton(-1), eventHandler); fail(); } catch (IllegalArgumentException expected) { } try { mProjectionManager.addKeyEventHandler( Collections.singleton(CarProjectionManager.NUM_KEY_EVENTS), eventHandler); fail(); } catch (IllegalArgumentException expected) { } } @Test public void keyEventHandler_whenRegisteredAgain_replacesEventList() { CarProjectionManager.ProjectionKeyEventHandler eventHandler = mock(CarProjectionManager.ProjectionKeyEventHandler.class); InOrder inOrder = inOrder(eventHandler); mProjectionManager.addKeyEventHandler( Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), DIRECT_EXECUTOR, eventHandler); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); inOrder.verify(eventHandler) .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); mProjectionManager.addKeyEventHandler( Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN), DIRECT_EXECUTOR, eventHandler); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); inOrder.verify(eventHandler, never()) .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); } @Test public void keyEventHandler_removed_noLongerFires() { CarProjectionManager.ProjectionKeyEventHandler eventHandler = mock(CarProjectionManager.ProjectionKeyEventHandler.class); mProjectionManager.addKeyEventHandler( Collections.singleton(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP), DIRECT_EXECUTOR, eventHandler); mProjectionManager.removeKeyEventHandler(eventHandler); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); verify(eventHandler, never()) .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); } @Test public void keyEventHandler_withAlternateExecutor_usesExecutor() { CarProjectionManager.ProjectionKeyEventHandler eventHandler = mock(CarProjectionManager.ProjectionKeyEventHandler.class); Executor executor = mock(Executor.class); ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); mProjectionManager.addKeyEventHandler( Collections.singleton( CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP), executor, eventHandler); mController.fireKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); verify(eventHandler, never()).onKeyEvent(anyInt()); verify(executor).execute(runnableCaptor.capture()); runnableCaptor.getValue().run(); verify(eventHandler) .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); } private static class ApCallback extends ProjectionAccessPointCallback { CountDownLatch mStarted = new CountDownLatch(1); CountDownLatch mFailed = new CountDownLatch(1); int mFailureReason = -1; SoftApConfiguration mSoftApConfiguration; WifiConfiguration mWifiConfiguration; @Override public void onStarted(WifiConfiguration wifiConfiguration) { mWifiConfiguration = wifiConfiguration; mStarted.countDown(); } @Override public void onStarted(SoftApConfiguration softApConfiguration) { mSoftApConfiguration = softApConfiguration; mStarted.countDown(); } @Override public void onStopped() { } @Override public void onFailed(int reason) { mFailureReason = reason; mFailed.countDown(); } } }