Run toolbar tests with and without shared library

Bug: 182208858
Test: atest CarUiLibUnitTests
Change-Id: I1c593c644ec3a1a53f47e2080b39d3ae0d4fd8b2
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
index 4c0d9bb..2030e0f 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
@@ -16,21 +16,28 @@
 
 package com.android.car.ui.matchers;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
 /* package */ class DrawableMatcher extends TypeSafeMatcher<View> {
 
-    private final int mDrawableId;
+    private final Bitmap mBitmap;
 
-    DrawableMatcher(int drawableId) {
-        mDrawableId = drawableId;
+    DrawableMatcher(@NonNull Context context, @DrawableRes int drawableId) {
+        this(context.getDrawable(drawableId));
+    }
+    DrawableMatcher(Drawable drawable) {
+        mBitmap = drawableToBitmap(drawable);
     }
 
     @Override
@@ -42,7 +49,7 @@
         ImageView imageView = (ImageView) item;
 
         Bitmap bitmap = drawableToBitmap(imageView.getDrawable());
-        Bitmap otherBitmap = drawableToBitmap(imageView.getContext().getDrawable(mDrawableId));
+        Bitmap otherBitmap = mBitmap;
 
         if (bitmap == null && otherBitmap == null) {
             return true;
@@ -68,6 +75,6 @@
 
     @Override
     public void describeTo(Description description) {
-        description.appendText("has drawable with id " + mDrawableId);
+        description.appendText("has a certain drawable");
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
index be397b1..9f4e238 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
@@ -16,15 +16,26 @@
 
 package com.android.car.ui.matchers;
 
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import static org.hamcrest.Matchers.not;
+
+import android.content.Context;
 import android.view.View;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.test.espresso.ViewAssertion;
+
 import com.android.car.ui.matchers.PaddingMatcher.Side;
 
 import org.hamcrest.Matcher;
 
 public class ViewMatchers {
-    public static Matcher<View> withDrawable(int drawableId) {
-        return new DrawableMatcher(drawableId);
+    public static Matcher<View> withDrawable(
+            @NonNull Context context, @DrawableRes int drawableId) {
+        return new DrawableMatcher(context, drawableId);
     }
 
     public static Matcher<View> nthChildOfView(Matcher<View> parentMatcher, int n) {
@@ -46,4 +57,12 @@
     public static Matcher<View> isActivated() {
         return new IsActivatedMatcher();
     }
+
+    public static ViewAssertion doesNotExistOrIsNotDisplayed() {
+        return (view, noViewFoundException) -> {
+            if (view != null) {
+                matches(not(isDisplayed())).check(view, noViewFoundException);
+            }
+        };
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
index b116706..eb576b3 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
@@ -22,6 +22,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
@@ -101,7 +102,7 @@
                 .check(matches(isNotChecked()));
 
         // Press back to save selection.
-        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
         // Verify preference value was updated.
         verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[0]));
 
@@ -126,7 +127,7 @@
                 .check(matches(isNotChecked()));
 
         // Press back to save selection.
-        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
         // Verify preference value was updated.
         verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[1]));
         // Return to list preference screen.
@@ -175,7 +176,7 @@
                 .check(matches(isChecked()));
 
         // Press back to save selection.
-        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
         Set<String> expectedUpdate = new HashSet<>();
         expectedUpdate.add(mEntriesValues[0]);
         expectedUpdate.add(mEntriesValues[2]);
@@ -318,7 +319,7 @@
                 .check(matches(isChecked()));
 
         // Press back to save selection.
-        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
         // Verify preference value was updated.
         verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[2]));
 
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
index eafbb30..b9e475f 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
@@ -18,16 +18,16 @@
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.hasChildCount;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withHint;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.car.ui.actions.ViewActions.waitForView;
-import static com.android.car.ui.matchers.ViewMatchers.nthChildOfView;
+import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 import static com.android.car.ui.matchers.ViewMatchers.withDrawable;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -38,28 +38,45 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.view.View;
 
-import androidx.test.rule.ActivityTestRule;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.ui.core.CarUi;
+import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
 import com.android.car.ui.test.R;
 
-import org.hamcrest.Matcher;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /** Unit test for {@link ToolbarController}. */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@RunWith(Parameterized.class)
 public class ToolbarTest {
 
+    @Parameterized.Parameters(name = "With shared library? {0}")
+    public static Object[] data() {
+        // It's important to do no shared library first, so that the shared library will
+        // still be enabled when this test finishes
+        return new Object[] { false, true };
+    }
+
+    public ToolbarTest(boolean sharedLibEnabled) {
+        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    }
+
     @Rule
-    public ActivityTestRule<ToolbarTestActivity> mActivityRule =
-            new ActivityTestRule<>(ToolbarTestActivity.class);
+    public final ActivityScenarioRule<ToolbarTestActivity> mScenarioRule =
+            new ActivityScenarioRule<>(ToolbarTestActivity.class);
 
     @Test
     public void test_setTitle_displaysTitle() throws Throwable {
@@ -135,157 +152,187 @@
     public void test_setLogo_displaysLogo() throws Throwable {
         runWithToolbar((toolbar) -> toolbar.setLogo(R.drawable.ic_launcher));
 
-        onView(withDrawable(R.drawable.ic_launcher)).check(matches(isDisplayed()));
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        onView(withDrawable(context, R.drawable.ic_launcher)).check(matches(isDisplayed()));
     }
 
     @Test
     public void pressBack_withoutListener_callsActivityOnBack() throws Throwable {
-        runWithToolbar((toolbar) -> toolbar.setState(Toolbar.State.SUBPAGE));
+        ToolbarTestActivity[] savedActivity = new ToolbarTestActivity[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            toolbar.setState(Toolbar.State.SUBPAGE);
+            savedActivity[0] = activity;
+        });
 
-        onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
 
-        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
+        assertEquals(1, savedActivity[0].getTimesOnBackPressed());
     }
 
     @Test
     public void pressBack_withListenerThatReturnsFalse_callsActivityOnBack() throws Throwable {
-        runWithToolbar((toolbar) -> {
+        ToolbarTestActivity[] savedActivity = new ToolbarTestActivity[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
             toolbar.setState(Toolbar.State.SUBPAGE);
             toolbar.registerOnBackListener(() -> false);
+            savedActivity[0] = activity;
         });
 
-        onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
 
-        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
+        assertEquals(1, savedActivity[0].getTimesOnBackPressed());
     }
 
     @Test
     public void pressBack_withListenerThatReturnsTrue_doesntCallActivityOnBack() throws Throwable {
-        runWithToolbar((toolbar) -> {
+        ToolbarTestActivity[] savedActivity = new ToolbarTestActivity[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
             toolbar.setState(Toolbar.State.SUBPAGE);
             toolbar.registerOnBackListener(() -> true);
+            savedActivity[0] = activity;
         });
 
-        onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
 
-        assertEquals(0, mActivityRule.getActivity().getTimesOnBackPressed());
+        assertEquals(0, savedActivity[0].getTimesOnBackPressed());
     }
 
     @Test
     public void pressBack_withUnregisteredListener_doesntCallActivityOnBack() throws Throwable {
-        runWithToolbar((toolbar) -> {
+        ToolbarTestActivity[] savedActivity = new ToolbarTestActivity[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
             toolbar.setState(Toolbar.State.SUBPAGE);
             Toolbar.OnBackListener listener = () -> true;
             toolbar.registerOnBackListener(listener);
             toolbar.registerOnBackListener(listener);
             toolbar.unregisterOnBackListener(listener);
+            savedActivity[0] = activity;
         });
 
-        onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
+        onView(withContentDescription("Back")).perform(click());
 
-        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
+        assertEquals(1, savedActivity[0].getTimesOnBackPressed());
     }
 
     @Test
-    public void menuItems_setId_shouldWork() {
-        MenuItem item = MenuItem.builder(mActivityRule.getActivity()).build();
+    public void menuItems_setId_shouldWork() throws Throwable {
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            MenuItem item = MenuItem.builder(activity).build();
 
-        assertThat(item.getId()).isEqualTo(View.NO_ID);
+            assertThat(item.getId()).isEqualTo(View.NO_ID);
 
-        item.setId(7);
+            item.setId(7);
 
-        assertThat(item.getId()).isEqualTo(7);
+            assertThat(item.getId()).isEqualTo(7);
+        });
     }
 
     @Test
     public void menuItems_whenClicked_shouldCallListener() throws Throwable {
         MenuItem.OnClickListener callback = mock(MenuItem.OnClickListener.class);
-        MenuItem menuItem = MenuItem.builder(mActivityRule.getActivity())
-                .setTitle("Button!")
-                .setOnClickListener(callback)
-                .build();
-        runWithToolbar((toolbar) -> toolbar.setMenuItems(Collections.singletonList(menuItem)));
+        MenuItem[] menuItem = new MenuItem[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            menuItem[0] = MenuItem.builder(activity)
+                    .setTitle("Button!")
+                    .setOnClickListener(callback)
+                    .build();
+            toolbar.setMenuItems(Collections.singletonList(menuItem[0]));
+        });
 
-        waitForMenuItems();
+        waitForViewWithText("Button!");
 
-        onView(firstMenuItem()).perform(click());
+        onView(withText("Button!")).perform(click());
 
-        verify(callback).onClick(menuItem);
+        verify(callback).onClick(menuItem[0]);
     }
 
     @Test
     public void menuItems_null_shouldRemoveExistingMenuItems() throws Throwable {
-        runWithToolbar((toolbar) ->
+        runWithActivityAndToolbar((activity, toolbar) ->
                 toolbar.setMenuItems(Arrays.asList(
-                        MenuItem.builder(mActivityRule.getActivity())
+                        MenuItem.builder(activity)
                                 .setTitle("Button!")
                                 .build(),
-                        MenuItem.builder(mActivityRule.getActivity())
+                        MenuItem.builder(activity)
                                 .setTitle("Button2!")
                                 .build()
                 )));
-        waitForMenuItems();
+        waitForViewWithText("Button!");
+        waitForViewWithText("Button2!");
 
-        onView(withId(R.id.car_ui_toolbar_menu_items_container)).check(matches(hasChildCount(2)));
+        onView(withText("Button!")).check(matches(isDisplayed()));
+        onView(withText("Button2!")).check(matches(isDisplayed()));
 
         runWithToolbar((toolbar) -> toolbar.setMenuItems(null));
 
-        onView(withId(R.id.car_ui_toolbar_menu_items_container)).check(matches(hasChildCount(0)));
+        onView(withText("Button!")).check(doesNotExist());
+        onView(withText("Button2!")).check(doesNotExist());
     }
 
     @Test
     public void menuItems_setVisibility_shouldHide() throws Throwable {
-        MenuItem menuItem = MenuItem.builder(mActivityRule.getActivity())
-                .setTitle("Button!")
-                .build();
-        runWithToolbar((toolbar) -> toolbar.setMenuItems(Collections.singletonList(menuItem)));
-        waitForMenuItems();
+        MenuItem[] menuItem = new MenuItem[] { null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            menuItem[0] = MenuItem.builder(activity)
+                    .setTitle("Button!")
+                    .build();
+            toolbar.setMenuItems(Collections.singletonList(menuItem[0]));
+        });
 
+        waitForViewWithText("Button!");
         onView(withText("Button!")).check(matches(isDisplayed()));
 
-        runWithToolbar((toolbar) -> menuItem.setVisible(false));
+        runWithToolbar((toolbar) -> menuItem[0].setVisible(false));
 
         onView(withText("Button!")).check(matches(not(isDisplayed())));
     }
 
     @Test
     public void menuItems_searchScreen_shouldHideMenuItems() throws Throwable {
-        runWithToolbar((toolbar) -> {
+        runWithActivityAndToolbar((activity, toolbar) -> {
             toolbar.setMenuItems(Arrays.asList(
-                    MenuItem.builder(mActivityRule.getActivity())
+                    MenuItem.builder(activity)
                             .setToSearch()
                             .build(),
-                    MenuItem.builder(mActivityRule.getActivity())
+                    MenuItem.builder(activity)
                             .setTitle("Button!")
                             .build()));
-            toolbar.setShowMenuItemsWhileSearching(false);
+            toolbar.setShowMenuItemsWhileSearching(true);
             toolbar.setState(Toolbar.State.SEARCH);
         });
-        waitForMenuItems();
 
-        // All menuitems should be hidden if we're hiding menuitems while searching
-        onView(withText("Button!")).check(matches(not(isDisplayed())));
-        onView(firstMenuItem()).check(matches(not(isDisplayed())));
-
-        runWithToolbar((toolbar) -> toolbar.setShowMenuItemsWhileSearching(true));
+        waitForViewWithText("Button!");
 
         // Even if not hiding MenuItems while searching, the search MenuItem should still be hidden
         onView(withText("Button!")).check(matches(isDisplayed()));
-        onView(firstMenuItem()).check(matches(not(isDisplayed())));
+        onView(withContentDescription(R.string.car_ui_toolbar_menu_item_search_title))
+                .check(doesNotExistOrIsNotDisplayed());
+
+        runWithToolbar((toolbar) -> toolbar.setShowMenuItemsWhileSearching(false));
+
+        // All menuitems should be hidden if we're hiding menuitems while searching
+        onView(withText("Button!")).check(doesNotExistOrIsNotDisplayed());
+        onView(withContentDescription(R.string.car_ui_toolbar_menu_item_search_title))
+                .check(doesNotExistOrIsNotDisplayed());
+
     }
 
     private void runWithToolbar(Consumer<ToolbarController> toRun) throws Throwable {
-        mActivityRule.runOnUiThread(() -> {
-            ToolbarController toolbar = CarUi.requireToolbar(mActivityRule.getActivity());
+        mScenarioRule.getScenario().onActivity(activity -> {
+            ToolbarController toolbar = CarUi.requireToolbar(activity);
             toRun.accept(toolbar);
         });
     }
 
-    private Matcher<View> firstMenuItem() {
-        return nthChildOfView(withId(R.id.car_ui_toolbar_menu_items_container), 0);
+    private void runWithActivityAndToolbar(BiConsumer<ToolbarTestActivity, ToolbarController> toRun)
+            throws Throwable {
+        mScenarioRule.getScenario().onActivity(activity -> {
+            ToolbarController toolbar = CarUi.requireToolbar(activity);
+            toRun.accept(activity, toolbar);
+        });
     }
 
-    private void waitForMenuItems() {
-        onView(isRoot()).perform(waitForView(firstMenuItem(), 500));
+    private void waitForViewWithText(String text) {
+        onView(isRoot()).perform(waitForView(withText(text), 500));
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java
index 3284aa0..69676f5 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.car.ui.R;
 import com.android.car.ui.utils.CarUiUtils;
@@ -47,6 +48,7 @@
 
     private static final String TAG = "carui";
     private static SharedLibraryFactory sInstance;
+    private static boolean sSharedLibEnabled = true;
 
     /**
      * Get the {@link SharedLibraryFactory}.
@@ -62,6 +64,11 @@
 
         context = context.getApplicationContext();
 
+        if (!sSharedLibEnabled) {
+            sInstance = new SharedLibraryFactoryStub(context);
+            return sInstance;
+        }
+
         String sharedLibPackageName = CarUiUtils.getSystemProperty(context.getResources(),
                 R.string.car_ui_shared_library_package_system_property_name);
 
@@ -138,6 +145,23 @@
         return sInstance;
     }
 
+    /**
+     * This method globally enables/disables the shared library. It only applies upon the next
+     * call to {@link #get}, components that have already been created won't switch between
+     * the shared/static library implementations.
+     * <p>
+     * This method is @VisibleForTesting so that unit tests can run both with and without
+     * the shared library. Since it's tricky to use correctly, real apps shouldn't use it.
+     * Instead, apps should use {@link SharedLibraryConfigProvider} to control if their
+     * shared library is disabled.
+     */
+    @VisibleForTesting
+    public static void setSharedLibEnabled(boolean sharedLibEnabled) {
+        sSharedLibEnabled = sharedLibEnabled;
+        // Cause the next call to get() to reinitialize the shared library
+        sInstance = null;
+    }
+
     private SharedLibraryFactorySingleton() {}
 
     @NonNull