Add ClusterHomeServiceUnitTest.

Bug: 190833183
Test: atest ClusterHomeServiceUnitTest
Change-Id: I6fd650e454a4d2c74e43031b3f60d3a815ae5d20
diff --git a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
new file mode 100644
index 0000000..4afe81e
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2021 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.car.cluster;
+
+import static android.car.CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_NONE;
+import static android.car.navigation.CarNavigationInstrumentCluster.CLUSTER_TYPE_IMAGE_CODES_ONLY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityOptions;
+import android.car.cluster.ClusterHomeManager;
+import android.car.cluster.ClusterState;
+import android.car.cluster.IClusterHomeCallback;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
+import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.Display;
+
+import com.android.car.CarOccupantZoneService;
+import com.android.car.am.FixedActivityService;
+import com.android.car.cluster.ClusterNavigationService.ContextOwner;
+import com.android.car.hal.ClusterHalService;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterHomeServiceUnitTest {
+    private static final int CLUSTER_DISPLAY_ID = 99;
+    private static final int CLUSTER_WIDTH = 1024;
+    private static final int CLUSTER_HEIGHT = 600;
+    private static final int UI_TYPE_CLUSTER_MAPS = UI_TYPE_CLUSTER_HOME + 1;
+    private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
+    private static final int USER_ID = 111;
+
+    private ClusterHomeService mClusterHomeService;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private ClusterHalService mClusterHalService;
+    @Mock
+    private ClusterNavigationService mNavigationService;
+    @Mock
+    private CarOccupantZoneService mOccupantZoneService;
+    @Mock
+    private FixedActivityService mFixedActivityService;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private Display mClusterDisplay;
+
+    private ClusterState mClusterState;
+    private int mClusterStateChanges;
+    private byte[] mNavigationState;
+
+    private IClusterHomeCallback mClusterHomeCallback;
+    private class IClusterHomeCallbackImpl extends IClusterHomeCallback.Stub {
+        @Override
+        public void onClusterStateChanged(ClusterState state, int changes) {
+            mClusterState = state;
+            mClusterStateChanges = changes;
+        }
+
+        @Override
+        public void onNavigationStateChanged(byte[] navigationState) {
+            mNavigationState = navigationState;
+        }
+    }
+
+    private ComponentName mClusterHomeActivity = new ComponentName("clusterhome.pkg", "activity");
+
+    @Before
+    public void setUp() {
+        when(mContext.getString(com.android.car.R.string.config_clusterHomeActivity))
+                .thenReturn(mClusterHomeActivity.flattenToString());
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+        when(mOccupantZoneService.getDisplayIdForDriver(DISPLAY_TYPE_INSTRUMENT_CLUSTER))
+                .thenReturn(CLUSTER_DISPLAY_ID);
+        when(mClusterHalService.isCoreSupported()).thenReturn(true);
+        when(mClusterHalService.isNavigationStateSupported()).thenReturn(true);
+        when(mDisplayManager.getDisplay(CLUSTER_DISPLAY_ID)).thenReturn(mClusterDisplay);
+        doAnswer(invocation -> {
+            Point size = (Point) invocation.getArgument(0);
+            size.set(CLUSTER_WIDTH, CLUSTER_HEIGHT);
+            return null;
+        }).when(mClusterDisplay).getRealSize(any(Point.class));
+
+        mClusterHomeService = new ClusterHomeService(mContext, mClusterHalService,
+                mNavigationService, mOccupantZoneService, mFixedActivityService);
+        mClusterHomeService.init();
+    }
+
+    public void registerClusterHomeCallback() {
+        mClusterHomeCallback = new IClusterHomeCallbackImpl();
+        mClusterHomeService.registerCallback(mClusterHomeCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mClusterHomeCallback != null) {
+            mClusterHomeService.unregisterCallback(mClusterHomeCallback);
+        }
+        mClusterHomeService.release();
+    }
+
+    @Test
+    public void initStartsClusterHomeActivity() {
+        // ClusterHomeService.init() was called in setUp().
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ActivityOptions> activityOptionsCaptor = ArgumentCaptor.forClass(
+                ActivityOptions.class);
+        verify(mFixedActivityService).startFixedActivityModeForDisplayAndUser(
+                intentCaptor.capture(), activityOptionsCaptor.capture(),
+                eq(CLUSTER_DISPLAY_ID), eq(UserHandle.USER_SYSTEM));
+        assertThat(intentCaptor.getValue().getComponent()).isEqualTo(mClusterHomeActivity);
+        assertThat(activityOptionsCaptor.getValue().getLaunchDisplayId())
+                .isEqualTo(CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void getClusterStateReturnsClusterState() {
+        ClusterState clusterState = mClusterHomeService.getClusterState();
+
+        assertThat(clusterState.on).isFalse();
+        assertThat(clusterState.bounds).isEqualTo(new Rect(0, 0, CLUSTER_WIDTH, CLUSTER_HEIGHT));
+        assertThat(clusterState.insets).isEqualTo(Insets.NONE);
+        assertThat(clusterState.uiType).isEqualTo(UI_TYPE_CLUSTER_HOME);
+        assertThat(clusterState.displayId).isEqualTo(CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void onSwitchUiSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        mClusterHomeService.onSwitchUi(UI_TYPE_CLUSTER_MAPS);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_UI_TYPE);
+        assertThat(mClusterState.uiType).isEqualTo(UI_TYPE_CLUSTER_MAPS);
+        assertThat(mClusterHomeService.getClusterState().uiType).isEqualTo(UI_TYPE_CLUSTER_MAPS);
+    }
+
+    @Test
+    public void displayOnSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        mClusterHomeService.onDisplayState(ClusterHalService.DISPLAY_ON,
+                /* bounds= */ null, /* insets= */ null);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_ON_OFF);
+        assertThat(mClusterState.on).isTrue();
+        assertThat(mClusterHomeService.getClusterState().on).isTrue();
+    }
+
+    @Test
+    public void displayBoundsSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        Rect newBounds = new Rect(10, 10, CLUSTER_WIDTH - 10, CLUSTER_HEIGHT - 10);
+        mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE,
+                newBounds, /* insets= */ null);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_BOUNDS);
+        assertThat(mClusterState.bounds).isEqualTo(newBounds);
+        assertThat(mClusterHomeService.getClusterState().bounds).isEqualTo(newBounds);
+    }
+
+    @Test
+    public void displayInsetsSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        Insets newInsets = Insets.of(10, 10, 10, 10);
+        mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE, /* bounds= */ null,
+                newInsets);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_INSETS);
+        assertThat(mClusterState.insets).isEqualTo(newInsets);
+        assertThat(mClusterHomeService.getClusterState().insets).isEqualTo(newInsets);
+    }
+
+    @Test
+    public void onNavigationStateChangedSendsNavigationState() {
+        registerClusterHomeCallback();
+
+        Bundle bundle = new Bundle();
+        byte[] newNavState = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+        bundle.putByteArray(NAV_STATE_PROTO_BUNDLE_KEY, newNavState);
+        mClusterHomeService.onNavigationStateChanged(bundle);
+
+        // mNavigationState is updated through mClusterHomeCallback.
+        assertThat(mNavigationState).isEqualTo(newNavState);
+
+        ArgumentCaptor<byte[]> navStateCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).sendNavigationState(navStateCaptor.capture());
+        assertThat(navStateCaptor.getValue()).isEqualTo(newNavState);
+    }
+
+    @Test
+    public void getInstrumentClusterInfoReturnsClusterInfo() {
+        CarNavigationInstrumentCluster clusterInfo = mClusterHomeService.getInstrumentClusterInfo();
+
+        assertThat(clusterInfo.getType()).isEqualTo(CLUSTER_TYPE_IMAGE_CODES_ONLY);
+    }
+
+    @Test
+    public void notifyNavContextOwnerChangedSendsNavigationState() throws
+            InvalidProtocolBufferException {
+        ContextOwner owner = new ContextOwner(/* uid= */ 123, /* pid= */ 456);
+        mClusterHomeService.notifyNavContextOwnerChanged(owner);
+
+        ArgumentCaptor<byte[]> navStateCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).sendNavigationState(navStateCaptor.capture());
+        NavigationStateProto navState = NavigationStateProto.parseFrom(navStateCaptor.getValue());
+        assertThat(navState.getServiceStatus())
+                .isEqualTo(NavigationStateProto.ServiceStatus.NORMAL);
+    }
+
+    @Test
+    public void reportStateInvokesHal() {
+        byte[] uiAvailability = new byte[] {(byte) 1, (byte) 1, (byte) 0, (byte) 1};
+        mClusterHomeService.reportState(UI_TYPE_CLUSTER_MAPS, UI_TYPE_CLUSTER_NONE, uiAvailability);
+
+        ArgumentCaptor<byte[]> uiAvailabilityCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).reportState(/* onOff= */ anyInt(), /* bounds= */ any(Rect.class),
+                any(Insets.class), eq(UI_TYPE_CLUSTER_MAPS), eq(UI_TYPE_CLUSTER_NONE),
+                uiAvailabilityCaptor.capture());
+        assertThat(uiAvailabilityCaptor.getValue()).isEqualTo(uiAvailability);
+    }
+
+    @Test
+    public void requestDisplayInvokesHal() {
+        mClusterHomeService.requestDisplay(UI_TYPE_CLUSTER_MAPS);
+
+        verify(mClusterHalService).requestDisplay(eq(UI_TYPE_CLUSTER_MAPS));
+    }
+
+    @Test
+    public void startFixedActivityModeAsUserInvokesFixedActivityService() {
+        Intent intent = Intent.makeMainActivity(
+                ComponentName.createRelative("test.pkg", "testClusterActivity"));
+        ActivityOptions activityOptions = ActivityOptions.makeBasic();
+        mClusterHomeService.startFixedActivityModeAsUser(
+                intent, activityOptions.toBundle(), USER_ID);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ActivityOptions> activityOptionsCaptor = ArgumentCaptor.forClass(
+                ActivityOptions.class);
+        verify(mFixedActivityService).startFixedActivityModeForDisplayAndUser(
+                intentCaptor.capture(), activityOptionsCaptor.capture(),
+                eq(CLUSTER_DISPLAY_ID), eq(USER_ID));
+        assertThat(intentCaptor.getValue()).isEqualTo(intent);
+        assertThat(activityOptionsCaptor.getValue().getLaunchDisplayId()).isEqualTo(
+                CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void stopFixedActivityModeInvokesFixedActivityService() {
+        mClusterHomeService.stopFixedActivityMode();
+
+        verify(mFixedActivityService).stopFixedActivityMode(eq(CLUSTER_DISPLAY_ID));
+    }
+}