Setup on device testing for sys ui

This borrows on the testing infrastructure set up by the phone sys ui
team and provides an examples via test for CarNavigationBarController.

Bug: 143227120
Test: atest CarNavigationBarControllerTest
Change-Id: I10fd4e2e51f2faf75ade5b40e47441a9e948f075
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 672879a..b2451c9 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -63,6 +63,64 @@
 
 }
 
+android_library {
+    name: "CarSystemUI-tests",
+    manifest: "tests/AndroidManifest.xml",
+    resource_dirs: [
+        "tests/res",
+        "res-keyguard",
+        "res",
+    ],
+    srcs: [
+        "tests/src/**/*.java",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+    static_libs: [
+        "SystemUI-tests",
+        "CarNotificationLib",
+        "SystemUIPluginLib",
+        "SystemUISharedLib",
+        "SettingsLib",
+        "android.car.userlib",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.recyclerview_recyclerview",
+        "androidx.preference_preference",
+        "androidx.appcompat_appcompat",
+        "androidx.mediarouter_mediarouter",
+        "androidx.palette_palette",
+        "androidx.legacy_legacy-preference-v14",
+        "androidx.leanback_leanback",
+        "androidx.slice_slice-core",
+        "androidx.slice_slice-view",
+        "androidx.slice_slice-builders",
+        "androidx.arch.core_core-runtime",
+        "androidx.lifecycle_lifecycle-extensions",
+        "SystemUI-tags",
+        "SystemUI-proto",
+        "metrics-helper-lib",
+        "androidx.test.rules", "hamcrest-library",
+        "mockito-target-inline-minus-junit4",
+        "testables",
+        "truth-prebuilt",
+        "dagger2-2.19",
+        "//external/kotlinc:kotlin-annotations",
+    ],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+        "android.car",
+    ],
+
+    aaptflags: [
+        "--extra-packages",
+        "com.android.systemui",
+    ],
+
+    plugins: ["dagger2-compiler-2.19"],
+}
+
 android_app {
     name: "CarSystemUI",
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
index 24f8b74..c245508 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.car.CarNavigationBarController.NotificationsShadeController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 
 /**
@@ -35,7 +36,7 @@
 public class CarNavigationBarView extends LinearLayout {
     private View mNavButtons;
     private CarNavigationButton mNotificationsButton;
-    private CarNavigationBarController.NotificationsShadeController mNotificationsShadeController;
+    private NotificationsShadeController mNotificationsShadeController;
     private Context mContext;
     private View mLockScreenButtons;
     // used to wire in open/close gestures for notifications
@@ -81,13 +82,18 @@
         return super.onInterceptTouchEvent(ev);
     }
 
-    public void setNotificationsPanelController(
-            CarNavigationBarController.NotificationsShadeController controller) {
+    /** Sets the notifications panel controller. */
+    public void setNotificationsPanelController(NotificationsShadeController controller) {
         mNotificationsShadeController = controller;
     }
 
+    /** Gets the notifications panel controller. */
+    public NotificationsShadeController getNotificationsPanelController() {
+        return mNotificationsShadeController;
+    }
+
     /**
-     * Set a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
+     * Sets a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
      *
      * @param statusBarWindowTouchListener The listener to call from touch and intercept touch
      */
@@ -95,6 +101,11 @@
         mStatusBarWindowTouchListener = statusBarWindowTouchListener;
     }
 
+    /** Gets the touch listener that will be called from onInterceptTouchEvent and onTouchEvent. */
+    public OnTouchListener getStatusBarWindowTouchListener() {
+        return mStatusBarWindowTouchListener;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (mStatusBarWindowTouchListener != null) {
diff --git a/packages/CarSystemUI/tests/Android.mk b/packages/CarSystemUI/tests/Android.mk
new file mode 100644
index 0000000..1366568
--- /dev/null
+++ b/packages/CarSystemUI/tests/Android.mk
@@ -0,0 +1,88 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
+
+LOCAL_PACKAGE_NAME := CarSystemUITests
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    CarSystemUI-tests
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libdexmakerjvmtiagent \
+    libmultiplejvmtiagentsinterferenceagent
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    telephony-common \
+    android.test.base \
+
+LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
+
+# sign this with platform cert, so this test is allowed to inject key events into
+# UI it doesn't own. This is necessary to allow screenshots to be taken
+LOCAL_CERTIFICATE := platform
+
+# Provide jack a list of classes to exclude from code coverage.
+# This is needed because the CarSystemUITests compile CarSystemUI source directly, rather than using
+# LOCAL_INSTRUMENTATION_FOR := CarSystemUI.
+#
+# We want to exclude the test classes from code coverage measurements, but they share the same
+# package as the rest of SystemUI so they can't be easily filtered by package name.
+#
+# Generate a comma separated list of patterns based on the test source files under src/
+# SystemUI classes are in ../src/ so they won't be excluded.
+# Example:
+#   Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
+#   Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
+
+# Filter all src files under src/ to just java files
+local_java_files := $(filter %.java,$(call all-java-files-under, src))
+
+# Transform java file names into full class names.
+# This only works if the class name matches the file name and the directory structure
+# matches the package.
+local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
+local_comma := ,
+local_empty :=
+local_space := $(local_empty) $(local_empty)
+
+# Convert class name list to jacoco exclude list
+# This appends a * to all classes and replace the space separators with commas.
+jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
+
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
+
+ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
+    include $(BUILD_PACKAGE)
+endif
+
+# Reset variables
+local_java_files :=
+local_classes :=
+local_comma :=
+local_space :=
+jacoco_exclude :=
diff --git a/packages/CarSystemUI/tests/AndroidManifest.xml b/packages/CarSystemUI/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a74bb56
--- /dev/null
+++ b/packages/CarSystemUI/tests/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          android:sharedUserId="android.uid.system"
+          package="com.android.systemui.tests">
+
+    <application android:debuggable="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner" />
+
+        <provider
+            android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
+            tools:replace="android:authorities"
+            android:authorities="${applicationId}.lifecycle-tests"
+            android:exported="false"
+            android:enabled="false"
+            android:multiprocess="true" />
+    </application>
+
+    <instrumentation android:name="android.testing.TestableInstrumentation"
+        android:targetPackage="com.android.systemui.tests"
+        android:label="Tests for CarSystemUI">
+    </instrumentation>
+</manifest>
diff --git a/packages/CarSystemUI/tests/AndroidTest.xml b/packages/CarSystemUI/tests/AndroidTest.xml
new file mode 100644
index 0000000..8685632
--- /dev/null
+++ b/packages/CarSystemUI/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="Runs Tests for CarSystemUI.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="CarSystemUITests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="CarSystemUITests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.systemui.tests" />
+        <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/packages/CarSystemUI/tests/res/values/config.xml b/packages/CarSystemUI/tests/res/values/config.xml
new file mode 100644
index 0000000..0d08ac2
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/values/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- Configure which system ui bars should be displayed.
+         These can be overwritten within the tests. -->
+    <bool name="config_enableLeftNavigationBar">false</bool>
+    <bool name="config_enableRightNavigationBar">false</bool>
+    <bool name="config_enableBottomNavigationBar">false</bool>
+</resources>
diff --git a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
new file mode 100644
index 0000000..fe59cbf
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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 com.android;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.internal.runner.ClassPathScanner;
+import androidx.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
+import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
+
+import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * This is named AAAPlusPlusVerifySysuiRequiredTestPropertiesTest for two reasons.
+ * a) Its so awesome it deserves an AAA++
+ * b) It should run first to draw attention to itself.
+ *
+ * For trues though: this test verifies that all the sysui tests extend the right classes.
+ * This matters because including tests with different context implementations in the same
+ * test suite causes errors, such as the incorrect settings provider being cached.
+ * For an example, see {@link com.android.systemui.DependencyTest}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
+
+    private static final String TAG = "AAA++VerifyTest";
+
+    private static final Class[] BASE_CLS_WHITELIST = {
+            SysuiTestCase.class,
+            SysuiBaseFragmentTest.class,
+    };
+
+    private static final Class[] SUPPORTED_SIZES = {
+            SmallTest.class,
+            MediumTest.class,
+            LargeTest.class,
+            android.test.suitebuilder.annotation.SmallTest.class,
+            android.test.suitebuilder.annotation.MediumTest.class,
+            android.test.suitebuilder.annotation.LargeTest.class,
+    };
+
+    @Test
+    public void testAllClassInheritance() throws Throwable {
+        ArrayList<String> fails = new ArrayList<>();
+        for (String className : getClassNamesFromClassPath()) {
+            Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader());
+            if (!isTestClass(cls)) continue;
+
+            boolean hasParent = false;
+            for (Class<?> parent : BASE_CLS_WHITELIST) {
+                if (parent.isAssignableFrom(cls)) {
+                    hasParent = true;
+                    break;
+                }
+            }
+            boolean hasSize = hasSize(cls);
+            if (!hasSize) {
+                fails.add(cls.getName() + " does not have size annotation, such as @SmallTest");
+            }
+            if (!hasParent) {
+                fails.add(cls.getName() + " does not extend any of " + getClsStr());
+            }
+        }
+
+        assertThat("All sysui test classes must have size and extend one of " + getClsStr(),
+                fails, is(empty()));
+    }
+
+    private boolean hasSize(Class<?> cls) {
+        for (int i = 0; i < SUPPORTED_SIZES.length; i++) {
+            if (cls.getDeclaredAnnotation(SUPPORTED_SIZES[i]) != null) return true;
+        }
+        return false;
+    }
+
+    private Collection<String> getClassNamesFromClassPath() {
+        ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
+
+        ChainedClassNameFilter filter = new ChainedClassNameFilter();
+
+        filter.add(new ExternalClassNameFilter());
+        filter.add(s -> s.startsWith("com.android.systemui")
+                || s.startsWith("com.android.keyguard"));
+
+        try {
+            return scanner.getClassPathEntries(filter);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to scan classes", e);
+        }
+        return Collections.emptyList();
+    }
+
+    private String getClsStr() {
+        return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
+                .stream().map(cls -> cls.getSimpleName()).toArray());
+    }
+
+    /**
+     * Determines if given class is a valid test class.
+     *
+     * @return <code>true</code> if loadedClass is a test
+     */
+    private boolean isTestClass(Class<?> loadedClass) {
+        try {
+            if (Modifier.isAbstract(loadedClass.getModifiers())) {
+                logDebug(String.format("Skipping abstract class %s: not a test",
+                        loadedClass.getName()));
+                return false;
+            }
+            // TODO: try to find upstream junit calls to replace these checks
+            if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
+                // ensure that if a TestCase, it has at least one test method otherwise
+                // TestSuite will throw error
+                if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
+                    return hasJUnit3TestMethod(loadedClass);
+                }
+                return true;
+            }
+            // TODO: look for a 'suite' method?
+            if (loadedClass.isAnnotationPresent(RunWith.class)) {
+                return true;
+            }
+            for (Method testMethod : loadedClass.getMethods()) {
+                if (testMethod.isAnnotationPresent(Test.class)) {
+                    return true;
+                }
+            }
+            logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
+            return false;
+        } catch (Exception e) {
+            // Defensively catch exceptions - Will throw runtime exception if it cannot load
+            // methods.
+            // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
+            // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
+            // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
+            // generic catch all.
+            // For ICS+, Dalvik will throw a NoClassDefFoundException.
+            Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        } catch (Error e) {
+            // defensively catch Errors too
+            Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        }
+    }
+
+    private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
+        for (Method testMethod : loadedClass.getMethods()) {
+            if (isPublicTestMethod(testMethod)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isPublicTestMethod(Method m) {
+        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isTestMethod(Method m) {
+        return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
+                && m.getReturnType().equals(Void.TYPE);
+    }
+
+    /**
+     * Utility method for logging debug messages. Only actually logs a message if TAG is marked
+     * as loggable to limit log spam during normal use.
+     */
+    private void logDebug(String msg) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
new file mode 100644
index 0000000..20ace44
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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 com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import dagger.Lazy;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class CarNavigationBarControllerTest extends SysuiTestCase {
+
+    private CarNavigationBarController mCarNavigationBar;
+    private NavigationBarViewFactory mNavigationBarViewFactory;
+    private Lazy<HvacController> mHvacControllerLazy;
+    private TestableResources mTestableResources;
+
+    @Mock
+    private HvacController mHvacController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mNavigationBarViewFactory = new NavigationBarViewFactory(mContext);
+        mHvacControllerLazy = () -> mHvacController;
+        mTestableResources = mContext.getOrCreateTestableResources();
+    }
+
+    @Test
+    public void testConnectToHvac_callsConnect() {
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.connectToHvac();
+
+        verify(mHvacController).connectToCarService();
+    }
+
+    @Test
+    public void testRemoveAllFromHvac_callsRemoveAll() {
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.removeAllFromHvac();
+
+        verify(mHvacController).removeAllComponents();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getBottomWindow();
+        ViewGroup window2 = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testGetLeftWindow_leftDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetLeftWindow_leftEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getLeftWindow();
+        ViewGroup window2 = mCarNavigationBar.getLeftWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testGetRightWindow_rightDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetRightWindow_rightEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getRightWindow();
+        ViewGroup window2 = mCarNavigationBar.getRightWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testSetBottomWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+        mCarNavigationBar.setBottomWindowVisibility(true);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetBottomWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+        mCarNavigationBar.setBottomWindowVisibility(false);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testSetLeftWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        mCarNavigationBar.setLeftWindowVisibility(true);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetLeftWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        mCarNavigationBar.setLeftWindowVisibility(false);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testSetRightWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+        mCarNavigationBar.setRightWindowVisibility(true);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetRightWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+        mCarNavigationBar.setRightWindowVisibility(false);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
+        assertThat(controller).isNull();
+        mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
+        controller = bottomBar.getStatusBarWindowTouchListener();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationBarController.NotificationsShadeController controller =
+                bottomBar.getNotificationsPanelController();
+        assertThat(controller).isNull();
+        mCarNavigationBar.registerNotificationController(
+                mock(CarNavigationBarController.NotificationsShadeController.class));
+        controller = bottomBar.getNotificationsPanelController();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.registerNotificationController(
+                mock(CarNavigationBarController.NotificationsShadeController.class));
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationBarController.NotificationsShadeController controller =
+                bottomBar.getNotificationsPanelController();
+
+        assertThat(controller).isNotNull();
+    }
+}