diff options
| author | 2020-06-04 16:06:43 -0700 | |
|---|---|---|
| committer | 2020-10-15 15:06:27 -0700 | |
| commit | b1369818a36feba921a25091c1c18c44ef9b6092 (patch) | |
| tree | 1f02397b38d9454e5fdb9a30d9c6618ad0f34822 | |
| parent | bc89f3ea7a1a8eb379f16f8b874727e928f4f669 (diff) | |
Add ReadViewCameraViewController/Mediator.
- Launch config_rearViewCameraActivity in the overlay Window when
gear is reversed.
- Add/Remove ActivityView dynamically to save the system resource
while it is hidden.
Bug: 157481389
Test: atest com.android.systemui.car.rvc
Test: Check if RearViewCamera is launched when the gear is reversed and if it will be closed when the gear is changed to the other one.
Change-Id: I768fe9090eed9cb82c4108ab8f6335802d36fa64
12 files changed, 608 insertions, 1 deletions
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml index c0482e1b3e04..f0cf2a9b7156 100644 --- a/packages/CarSystemUI/AndroidManifest.xml +++ b/packages/CarSystemUI/AndroidManifest.xml @@ -23,6 +23,8 @@ <uses-permission android:name="android.car.permission.CAR_POWER" /> <!-- This permission is required to get the trusted device list of a user. --> <uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/> + <!-- This permission is required to monitor gear state. --> + <uses-permission android:name="android.car.permission.CAR_POWERTRAIN" /> <!-- This permission is required to get bluetooth broadcast. --> <uses-permission android:name="android.permission.BLUETOOTH" /> <!-- These permissions are required to implement icons based on role holders. --> diff --git a/packages/CarSystemUI/res/layout/rear_view_camera.xml b/packages/CarSystemUI/res/layout/rear_view_camera.xml new file mode 100644 index 000000000000..9b9898cf3f45 --- /dev/null +++ b/packages/CarSystemUI/res/layout/rear_view_camera.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/rear_view_camera_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/transparent" + android:orientation="vertical"> + <Button + android:id="@+id/close_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/rear_view_camera_button_background" + android:text="@string/rear_view_camera_close_button_text" + android:textAppearance="?android:attr/textAppearanceLarge"/> +</LinearLayout> diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index 2c9788955bfa..980265e84e54 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -42,4 +42,10 @@ android:layout_height="match_parent" android:layout="@layout/car_user_switching_dialog"/> -</FrameLayout>
\ No newline at end of file + <!-- Should be at bottom to get the highest Z-order. --> + <ViewStub android:id="@+id/rear_view_camera_stub" + android:layout_width="@dimen/rear_view_camera_width" + android:layout_height="@dimen/rear_view_camera_height" + android:layout_gravity="center" + android:layout="@layout/rear_view_camera"/> +</FrameLayout> diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml index 0181b9a39aea..91d416623538 100644 --- a/packages/CarSystemUI/res/values/colors.xml +++ b/packages/CarSystemUI/res/values/colors.xml @@ -66,4 +66,6 @@ <color name="car_user_switching_dialog_background_color">@android:color/black</color> <color name="car_user_switching_dialog_loading_text_color">@*android:color/car_body1</color> + + <color name="rear_view_camera_button_background">@*android:color/car_card_dark</color> </resources> diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 949a0fac9598..8667ba1cd1a5 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -108,6 +108,7 @@ <item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item> <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item> <item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item> + <item>com.android.systemui.car.rvc.RearViewCameraViewMediator</item> </string-array> <!-- @@ -150,4 +151,7 @@ <!-- How many milliseconds to wait before force hiding the UserSwitchTransitionView --> <integer name="config_userSwitchTransitionViewShownTimeoutMs" translatable="false">5000</integer> + + <!-- The Activity name for the Rear View Camera, if empty, the feature will be disabled. --> + <string name="config_rearViewCameraActivity" translatable="false"></string> </resources> diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index 86bfa751d085..a7d8ab5f2a4c 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -227,4 +227,8 @@ <dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen> <dimen name="car_user_switching_dialog_loading_text_margin_top">@*android:dimen/car_padding_4</dimen> <dimen name="car_user_switching_dialog_loading_text_font_size">@*android:dimen/car_body1_size</dimen> + + <!-- dimensions for rear view camera --> + <dimen name="rear_view_camera_width">600dp</dimen> + <dimen name="rear_view_camera_height">500dp</dimen> </resources> diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml index 06ae7cfd6d1b..264456588c80 100644 --- a/packages/CarSystemUI/res/values/strings.xml +++ b/packages/CarSystemUI/res/values/strings.xml @@ -42,4 +42,6 @@ <string name="car_loading_profile">Loading</string> <!-- Message to inform user that the new user profile is loading with additional information on the previous and the next user. [CHAR LIMIT=100] --> <string name="car_loading_profile_developer_message">Loading user (from <xliff:g id="from_user" example="10">%1$d</xliff:g> to <xliff:g id="to_user" example="12">%2$d</xliff:g>)</string> + <!-- Text for the close button in Rear View Camera [CHAR LIMIT=30] --> + <string name="rear_view_camera_close_button_text">Close</string> </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewController.java new file mode 100644 index 000000000000..d63463309ec5 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewController.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.car.rvc; + +import android.app.ActivityView; +import android.app.ActivityView.StateCallback; +import android.content.ComponentName; +import android.content.Intent; +import android.content.res.Resources; +import android.util.Slog; +import android.view.ViewGroup; +import android.widget.LinearLayout.LayoutParams; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; +import com.android.systemui.car.window.OverlayViewController; +import com.android.systemui.car.window.OverlayViewGlobalStateController; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; + +import javax.inject.Inject; + +/** View controller for the rear view camera. */ +@SysUISingleton +public class RearViewCameraViewController extends OverlayViewController { + private static final String TAG = "RearViewCameraView"; + private static final boolean DBG = false; + + private final ComponentName mRearViewCameraActivity; + private ViewGroup mRvcView; + private final LayoutParams mRvcViewLayoutParams = new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, /* weight= */ 1.0f); + @VisibleForTesting + ActivityView mActivityView; + @VisibleForTesting + final StateCallback mActivityViewCallback = new StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + Intent intent = new Intent(Intent.ACTION_MAIN) + .setComponent(mRearViewCameraActivity) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) + .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + // TODO(b/170899079): Migrate this to FixedActivityService. + view.startActivity(intent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) {} + }; + + @Inject + public RearViewCameraViewController( + @Main Resources resources, + OverlayViewGlobalStateController overlayViewGlobalStateController) { + super(R.id.rear_view_camera_stub, overlayViewGlobalStateController); + String rearViewCameraActivityName = resources.getString( + R.string.config_rearViewCameraActivity); + if (!rearViewCameraActivityName.isEmpty()) { + mRearViewCameraActivity = ComponentName.unflattenFromString(rearViewCameraActivityName); + if (DBG) Slog.d(TAG, "mRearViewCameraActivity=" + mRearViewCameraActivity); + } else { + mRearViewCameraActivity = null; + Slog.e(TAG, "RearViewCameraViewController is disabled, since no Activity is defined"); + } + } + + @Override + protected void onFinishInflate() { + mRvcView = (ViewGroup) getLayout().findViewById(R.id.rear_view_camera_container); + getLayout().findViewById(R.id.close_button).setOnClickListener(v -> { + stop(); + }); + } + + @Override + protected void hideInternal() { + super.hideInternal(); + if (DBG) Slog.d(TAG, "hideInternal: mActivityView=" + mActivityView); + if (mActivityView == null) return; + mRvcView.removeView(mActivityView); + // Release ActivityView since the Activity on ActivityView (with showWhenLocked flag) keeps + // running even if ActivityView is hidden. + mActivityView.release(); + mActivityView = null; + } + + @Override + protected void showInternal() { + super.showInternal(); + if (DBG) Slog.d(TAG, "showInternal: mActivityView=" + mActivityView); + if (mActivityView != null) return; + mActivityView = new ActivityView(mRvcView.getContext()); + mActivityView.setCallback(mActivityViewCallback); + mActivityView.setLayoutParams(mRvcViewLayoutParams); + mRvcView.addView(mActivityView, /* index= */ 0); + } + + boolean isShown() { + return mActivityView != null; + } + + boolean isEnabled() { + return mRearViewCameraActivity != null; + } + + @Override + protected boolean shouldShowHUN() { + return false; + } + + @Override + protected boolean shouldShowWhenOccluded() { + // Returns true to show it on top of Keylock. + return true; + } + + @Override + protected boolean shouldShowNavigationBarInsets() { + return true; + } + + @Override + protected boolean shouldShowStatusBarInsets() { + return true; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewMediator.java new file mode 100644 index 000000000000..c575c423b256 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/rvc/RearViewCameraViewMediator.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.car.rvc; + +import android.car.Car; +import android.car.VehicleGear; +import android.car.VehiclePropertyIds; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyManager; +import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.window.OverlayViewMediator; +import com.android.systemui.dagger.SysUISingleton; + +import javax.inject.Inject; + +/** + * View mediator for the rear view camera (RVC), which monitors the gear changes and shows + * the RVC when the gear position is R and otherwise it hides the RVC. + */ +@SysUISingleton +public class RearViewCameraViewMediator implements OverlayViewMediator { + private static final String TAG = "RearViewCameraView"; + private static final boolean DBG = false; + + private final RearViewCameraViewController mRearViewCameraViewController; + private final CarServiceProvider mCarServiceProvider; + private final BroadcastDispatcher mBroadcastDispatcher; + + private CarPropertyManager mCarPropertyManager; + // TODO(b/170792252): Replace the following with the callback from CarEvsManager if it's ready. + private final CarPropertyEventCallback mPropertyEventCallback = new CarPropertyEventCallback() { + @Override + public void onChangeEvent(CarPropertyValue value) { + if (DBG) Slog.d(TAG, "onChangeEvent value=" + value); + if (value.getPropertyId() != VehiclePropertyIds.GEAR_SELECTION) { + Slog.w(TAG, "Got the event for non-registered property: " + value.getPropertyId()); + return; + } + if ((Integer) value.getValue() == VehicleGear.GEAR_REVERSE) { + mRearViewCameraViewController.start(); + } else { + mRearViewCameraViewController.stop(); + } + } + @Override + public void onErrorEvent(int propId, int zone) { + Slog.e(TAG, "onErrorEvent propId=" + propId + ", zone=" + zone); + } + }; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DBG) Slog.d(TAG, "onReceive: " + intent); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) + && mRearViewCameraViewController.isShown()) { + mRearViewCameraViewController.stop(); + } + } + }; + + @Inject + public RearViewCameraViewMediator( + RearViewCameraViewController rearViewCameraViewController, + CarServiceProvider carServiceProvider, + BroadcastDispatcher broadcastDispatcher) { + if (DBG) Slog.d(TAG, "RearViewCameraViewMediator:init"); + mRearViewCameraViewController = rearViewCameraViewController; + mCarServiceProvider = carServiceProvider; + mBroadcastDispatcher = broadcastDispatcher; + } + + @Override + public void registerListeners() { + if (DBG) Slog.d(TAG, "RearViewCameraViewMediator:registerListeners"); + if (!mRearViewCameraViewController.isEnabled()) { + Slog.i(TAG, "RearViewCameraViewController isn't enabled"); + return; + } + + mCarServiceProvider.addListener(car -> { + mCarPropertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE); + if (mCarPropertyManager == null) { + Slog.e(TAG, "Unable to get CarPropertyManager"); + return; + } + if (DBG) Slog.d(TAG, "Registering mPropertyEventCallback."); + mCarPropertyManager.registerCallback(mPropertyEventCallback, + VehiclePropertyIds.GEAR_SELECTION, CarPropertyManager.SENSOR_RATE_UI); + }); + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), /* executor= */ null, + UserHandle.ALL); + } + + @Override + public void setupOverlayContentViewControllers() {} +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java index 5a16efa3dd9b..fcbb0b807e74 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java @@ -20,6 +20,7 @@ import com.android.systemui.car.keyguard.CarKeyguardViewMediator; import com.android.systemui.car.notification.BottomNotificationPanelViewMediator; import com.android.systemui.car.notification.NotificationPanelViewMediator; import com.android.systemui.car.notification.TopNotificationPanelViewMediator; +import com.android.systemui.car.rvc.RearViewCameraViewMediator; import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator; import com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator; @@ -75,4 +76,11 @@ public abstract class OverlayWindowModule { @ClassKey(UserSwitchTransitionViewMediator.class) public abstract OverlayViewMediator bindUserSwitchTransitionViewMediator( UserSwitchTransitionViewMediator userSwitchTransitionViewMediator); + + /** Injects RearViewCameraViewMediator. */ + @Binds + @IntoMap + @ClassKey(RearViewCameraViewMediator.class) + public abstract OverlayViewMediator bindRearViewCameraViewMediator( + RearViewCameraViewMediator overlayViewsMediator); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewControllerTest.java new file mode 100644 index 000000000000..a6160ecf1c62 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewControllerTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.car.rvc; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; + +import android.app.ActivityView; +import android.content.ComponentName; +import android.content.Intent; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarSystemUiTest; +import com.android.systemui.car.window.OverlayViewGlobalStateController; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@CarSystemUiTest +@RunWith(MockitoJUnitRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class RearViewCameraViewControllerTest extends SysuiTestCase { + private static final String TEST_ACTIVITY_NAME = "testPackage/testActivity"; + private RearViewCameraViewController mRearViewCameraViewController; + + @Mock + private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + @Mock + private ActivityView mMockActivityView; + @Captor + private ArgumentCaptor<Intent> mIntentCaptor; + + private void setUpRearViewCameraViewController(String testActivityName) { + mContext.getOrCreateTestableResources().addOverride( + R.string.config_rearViewCameraActivity, testActivityName); + mRearViewCameraViewController = new RearViewCameraViewController( + mContext.getOrCreateTestableResources().getResources(), + mOverlayViewGlobalStateController); + mRearViewCameraViewController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.sysui_overlay_window, /* root= */ null)); + } + + @Test + public void testEmptyResourceDisablesController() { + setUpRearViewCameraViewController(""); + + assertThat(mRearViewCameraViewController.isEnabled()).isFalse(); + } + + @Test + public void testNonEmptyResourceEnablesController() { + setUpRearViewCameraViewController(TEST_ACTIVITY_NAME); + + assertThat(mRearViewCameraViewController.isEnabled()).isTrue(); + } + + @Test + public void testShowInternal() { + setUpRearViewCameraViewController(TEST_ACTIVITY_NAME); + assertThat(mRearViewCameraViewController.isShown()).isFalse(); + assertThat(mRearViewCameraViewController.mActivityView).isNull(); + + mRearViewCameraViewController.showInternal(); + + assertThat(mRearViewCameraViewController.isShown()).isTrue(); + assertThat(mRearViewCameraViewController.mActivityView).isNotNull(); + } + + @Test + public void testHideInternal() { + setUpRearViewCameraViewController(TEST_ACTIVITY_NAME); + assertThat(mRearViewCameraViewController.isShown()).isFalse(); + mRearViewCameraViewController.showInternal(); + assertThat(mRearViewCameraViewController.isShown()).isTrue(); + + mRearViewCameraViewController.hideInternal(); + + assertThat(mRearViewCameraViewController.isShown()).isFalse(); + assertThat(mRearViewCameraViewController.mActivityView).isNull(); + } + + @Test + public void testOnActivityViewReady_fireIntent() { + setUpRearViewCameraViewController(TEST_ACTIVITY_NAME); + mRearViewCameraViewController.mActivityViewCallback.onActivityViewReady(mMockActivityView); + + verify(mMockActivityView).startActivity(mIntentCaptor.capture()); + ComponentName expectedComponent = ComponentName.unflattenFromString(TEST_ACTIVITY_NAME); + assertThat(mIntentCaptor.getValue().getComponent()).isEqualTo(expectedComponent); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewMediatorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewMediatorTest.java new file mode 100644 index 000000000000..5be8f91ff430 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/rvc/RearViewCameraViewMediatorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.car.rvc; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.car.Car; +import android.car.VehicleAreaType; +import android.car.VehicleGear; +import android.car.VehiclePropertyIds; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyManager; +import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.testing.TestableLooper; +import android.util.Log; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.CarServiceProvider.CarServiceOnConnectedListener; +import com.android.systemui.car.CarSystemUiTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@CarSystemUiTest +@RunWith(MockitoJUnitRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class RearViewCameraViewMediatorTest extends SysuiTestCase { + private static final String TAG = RearViewCameraViewMediatorTest.class.getSimpleName(); + + private RearViewCameraViewMediator mRearViewCameraViewMediator; + + @Mock + private CarServiceProvider mCarServiceProvider; + @Mock + private Car mCar; + @Mock + private CarPropertyManager mCarPropertyManager; + @Captor + private ArgumentCaptor<CarPropertyEventCallback> mCarPropertyEventCallbackCaptor; + + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Captor + private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; + @Captor + private ArgumentCaptor<IntentFilter> mIntentFilterCaptor; + + @Mock + private RearViewCameraViewController mRearViewCameraViewController; + + @Before + public void setUp() throws Exception { + mRearViewCameraViewMediator = new RearViewCameraViewMediator( + mRearViewCameraViewController, mCarServiceProvider, mBroadcastDispatcher); + } + + public void setUpListener() { + doAnswer(invocation -> { + CarServiceOnConnectedListener listener = invocation.getArgument(0); + listener.onConnected(mCar); + return null; + }).when(mCarServiceProvider).addListener(any(CarServiceOnConnectedListener.class)); + when(mCar.getCarManager(Car.PROPERTY_SERVICE)).thenReturn(mCarPropertyManager); + when(mRearViewCameraViewController.isEnabled()).thenReturn(true); + + mRearViewCameraViewMediator.registerListeners(); + + verify(mCarPropertyManager).registerCallback(mCarPropertyEventCallbackCaptor.capture(), + eq(VehiclePropertyIds.GEAR_SELECTION), anyFloat()); + verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), + mIntentFilterCaptor.capture(), any(), any()); + assertThat(mIntentFilterCaptor.getValue().getAction(0)).isEqualTo( + Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + } + + @Test + public void testDoesnNotRegisterListenersWhenRearViewCameraViewControllerIsDisabled() { + when(mRearViewCameraViewController.isEnabled()).thenReturn(false); + + mRearViewCameraViewMediator.registerListeners(); + + verify(mCarPropertyManager, never()).registerCallback(any(), anyInt(), anyFloat()); + verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), any()); + } + + @Test + public void testGearReverseStartsRearViewCamera() { + setUpListener(); + + CarPropertyValue<Integer> gearReverse = new CarPropertyValue( + VehiclePropertyIds.GEAR_SELECTION, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, + VehicleGear.GEAR_REVERSE); + mCarPropertyEventCallbackCaptor.getValue().onChangeEvent(gearReverse); + + verify(mRearViewCameraViewController, times(1)).start(); + } + + @Test + public void testGearNonReverseStopsRearViewCamera() { + setUpListener(); + + int[] nonReverseVehicleGears = new int[]{ + VehicleGear.GEAR_NEUTRAL, VehicleGear.GEAR_PARK, VehicleGear.GEAR_DRIVE, + VehicleGear.GEAR_FIRST + }; + for (int i = 0; i < nonReverseVehicleGears.length; ++i) { + Log.i(TAG, "testGearNonReverseStopsRearViewCamera: gear=" + nonReverseVehicleGears[i]); + CarPropertyValue<Integer> propertyGear = new CarPropertyValue( + VehiclePropertyIds.GEAR_SELECTION, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, + nonReverseVehicleGears[i]); + mCarPropertyEventCallbackCaptor.getValue().onChangeEvent(propertyGear); + + verify(mRearViewCameraViewController, times(i + 1)).stop(); + } + } + + @Test + public void testBroadcastIntentStopsRearViewCamera() { + setUpListener(); + when(mRearViewCameraViewController.isShown()).thenReturn(true); + + Intent randomIntent = new Intent(Intent.ACTION_MAIN); + mBroadcastReceiverCaptor.getValue().onReceive(mContext, randomIntent); + + verify(mRearViewCameraViewController, never()).stop(); + + Intent actionCloseSystemDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + mBroadcastReceiverCaptor.getValue().onReceive(mContext, actionCloseSystemDialogs); + + verify(mRearViewCameraViewController, times(1)).stop(); + } +} |