Merge "Don't display UI when playback state is STATE_NONE" into pi-car-dev
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
index f346de0..9b13438 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
@@ -21,7 +21,6 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Size;
-import android.view.View;
import android.widget.ImageView;
import com.android.car.apps.common.CommonFlags;
@@ -57,7 +56,6 @@
protected void setDrawable(@Nullable Drawable drawable) {
if (mImageView != null) {
mImageView.setImageDrawable(drawable);
- mImageView.setVisibility((drawable != null) ? View.VISIBLE : View.GONE);
if (mFlagBitmaps) {
CommonFlags flags = CommonFlags.getInstance(mImageView.getContext());
if (flags.shouldFlagImproperImageRefs()) {
diff --git a/car-arch-common/src/com/android/car/arch/common/preference/PreferenceLiveData.java b/car-arch-common/src/com/android/car/arch/common/preference/PreferenceLiveData.java
deleted file mode 100644
index c9a9972..0000000
--- a/car-arch-common/src/com/android/car/arch/common/preference/PreferenceLiveData.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2018 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.arch.common.preference;
-
-import android.content.SharedPreferences;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LiveData;
-
-import java.util.Objects;
-
-/**
- * A LiveData that populates its value from {@link SharedPreferences}. When active, this LiveData
- * will listen for changes to the supplied key and update its value.
- *
- * @param <T> The type this PreferenceLiveData will emit.
- */
-public abstract class PreferenceLiveData<T> extends LiveData<T> {
-
- private final SharedPreferences mPreferences;
- private final String mKey;
-
- private final SharedPreferences.OnSharedPreferenceChangeListener mListener =
- (sharedPreferences, key) -> {
- if (Objects.equals(key, PreferenceLiveData.this.mKey)) {
- setValue(fetchValue(sharedPreferences, key));
- }
- };
-
- public PreferenceLiveData(
- @NonNull SharedPreferences preferences, @NonNull String key) {
- mPreferences = preferences;
- mKey = key;
- }
-
- /**
- * Subclasses should extract the required value from {@code preferences} corresponding to the
- * given {@code key} and return it. Subclasses should not directly call {@link #setValue(T)}
- * since it will be called when appropriate.
- */
- protected abstract T fetchValue(@NonNull SharedPreferences preferences, @NonNull String key);
-
- @Override
- protected void onActive() {
- super.onActive();
- setValue(fetchValue(mPreferences, mKey));
- mPreferences.registerOnSharedPreferenceChangeListener(mListener);
- }
-
- @Override
- protected void onInactive() {
- super.onInactive();
- mPreferences.unregisterOnSharedPreferenceChangeListener(mListener);
- }
-}
diff --git a/car-arch-common/src/com/android/car/arch/common/preference/StringPreferenceLiveData.java b/car-arch-common/src/com/android/car/arch/common/preference/StringPreferenceLiveData.java
deleted file mode 100644
index 79f56ac..0000000
--- a/car-arch-common/src/com/android/car/arch/common/preference/StringPreferenceLiveData.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2018 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.arch.common.preference;
-
-import android.content.SharedPreferences;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * A LiveData whose value is backed by a String preference in a {@link SharedPreferences}
- */
-public class StringPreferenceLiveData extends PreferenceLiveData<String> {
-
- private final String mDefaultValue;
-
- /**
- * Creates a new StringPreferenceLiveData.
- *
- * @param preferences The SharedPreferences to fetch data from.
- * @param key The String key to find the data.
- * @param defaultValue The value to emit when {@code preferences} does not contain data for the
- * given key
- */
- public StringPreferenceLiveData(
- @NonNull SharedPreferences preferences, @NonNull String key,
- @Nullable String defaultValue) {
- super(preferences, key);
- mDefaultValue = defaultValue;
- }
-
- @Override
- protected String fetchValue(@NonNull SharedPreferences preferences, @NonNull String key) {
- return preferences.getString(key, mDefaultValue);
- }
-}
diff --git a/car-media-common/res/values/arrays.xml b/car-media-common/res/values/arrays.xml
index 7bd071c..f9c1da2 100644
--- a/car-media-common/res/values/arrays.xml
+++ b/car-media-common/res/values/arrays.xml
@@ -36,4 +36,12 @@
<item>@color/placeholder_color_6</item>
<item>@color/placeholder_color_7</item>
</array>
+
+
+ <!-- These media sources are displayed by the AppSelectionFragment in the order defined here.
+ Other sources follow in alphabetical order. -->
+ <string-array name="preferred_media_sources" translatable="false">
+ <item>com.android.car.radio/.service.RadioAppService</item>
+ </string-array>
+
</resources>
\ No newline at end of file
diff --git a/car-media-common/src/com/android/car/media/common/MediaConstants.java b/car-media-common/src/com/android/car/media/common/MediaConstants.java
index c7520db..967680c 100644
--- a/car-media-common/src/com/android/car/media/common/MediaConstants.java
+++ b/car-media-common/src/com/android/car/media/common/MediaConstants.java
@@ -120,15 +120,37 @@
/**
* These constants are from
* @see <a href=https://developer.android.com/training/auto/audio/#required-actions></a>
+ *
+ * @deprecated this flag has been replaced by {@link #PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT}
*/
+ @Deprecated
public static final String SLOT_RESERVATION_SKIP_TO_NEXT =
"com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
+ /**
+ * @deprecated this flag has been replaced by {@link #PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT}
+ */
+ @Deprecated
public static final String SLOT_RESERVATION_SKIP_TO_PREV =
"com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
+ /**
+ * @deprecated this flag has been replaced by {@link #PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT}
+ */
+ @Deprecated
public static final String SLOT_RESERVATION_QUEUE =
"com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
/**
+ * These constants are from
+ * @see <a href=https://developer.android.com/training/auto/audio/#required-actions></a>
+ */
+ public static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT =
+ "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
+ public static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV =
+ "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
+ public static final String PLAYBACK_SLOT_RESERVATION_QUEUE =
+ "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
+
+ /**
* Bundle extra of type 'boolean' indicating that an item should show the 'explicit' symbol.
*/
public static final String EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT";
diff --git a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
index 5ca60d1..00a4416 100644
--- a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
+++ b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
@@ -236,6 +236,13 @@
}
/**
+ * @return optional extras that can include extra information about the media item to be played.
+ */
+ public Bundle getExtras() {
+ return mMediaDescription.getExtras();
+ }
+
+ /**
* @return boolean that indicate if media is explicit.
*/
public boolean isExplicit() {
diff --git a/car-media-common/src/com/android/car/media/common/MetadataController.java b/car-media-common/src/com/android/car/media/common/MetadataController.java
index 341c129..49e5a26 100644
--- a/car-media-common/src/com/android/car/media/common/MetadataController.java
+++ b/car-media-common/src/com/android/car/media/common/MetadataController.java
@@ -120,6 +120,8 @@
ViewUtils.setVisible(artist, !TextUtils.isEmpty(artistName));
}
+ ViewUtils.setVisible(albumArt, true);
+
mAlbumArtBinder.setImage(context, metadata.getArtworkKey());
});
diff --git a/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java b/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
index b23b652..5a655ef 100644
--- a/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
+++ b/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
@@ -600,12 +600,12 @@
}
/**
- * Starts playing a given media item. This id corresponds to {@link
- * MediaItemMetadata#getId()}.
+ * Starts playing a given media item.
*/
- public void playItem(String mediaItemId) {
+ public void playItem(MediaItemMetadata item) {
if (mMediaController != null) {
- mMediaController.getTransportControls().playFromMediaId(mediaItemId, null);
+ mMediaController.getTransportControls().playFromMediaId(item.getId(),
+ item.getExtras());
}
}
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSource.java b/car-media-common/src/com/android/car/media/common/source/MediaSource.java
index 09dcbb5..bbc96dc 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSource.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSource.java
@@ -167,8 +167,7 @@
}
/**
- * @return a {@link ComponentName} referencing this media source's {@link MediaBrowserService},
- * or NULL if this media source doesn't implement such service.
+ * @return a {@link ComponentName} referencing this media source's {@link MediaBrowserService}.
*/
@NonNull
public ComponentName getBrowseServiceComponentName() {
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java b/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java
index 6abdab2..7d423c8 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java
@@ -30,7 +30,12 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.car.media.common.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -86,10 +91,27 @@
mAppContext.registerReceiver(mAppInstallUninstallReceiver, filter);
}
- /** Returns the alphabetically sorted list of available media sources. */
+ /**
+ * Returns the sorted list of available media sources. Sources listed in the array resource
+ * R.array.preferred_media_sources are included first. Other sources follow in alphabetical
+ * order.
+ */
public List<MediaSource> getList() {
if (mMediaSources == null) {
- mMediaSources = getComponentNames().stream()
+ // Get the flattened components to display first.
+ String[] preferredFlats = mAppContext.getResources().getStringArray(
+ R.array.preferred_media_sources);
+
+ // Make a map of components to display first (the value is the component's index).
+ HashMap<ComponentName, Integer> preferredComps = new HashMap<>(preferredFlats.length);
+ for (int i = 0; i < preferredFlats.length; i++) {
+ preferredComps.put(ComponentName.unflattenFromString(preferredFlats[i]), i);
+ }
+
+ // Prepare an array of the sources to display first (unavailable preferred components
+ // will be excluded).
+ MediaSource[] preferredSources = new MediaSource[preferredFlats.length];
+ List<MediaSource> sortedSources = getComponentNames().stream()
.filter(Objects::nonNull)
.map(componentName -> MediaSource.create(mAppContext, componentName))
.filter(mediaSource -> {
@@ -97,11 +119,23 @@
Log.w(TAG, "Media source is null");
return false;
}
+ ComponentName srcComp = mediaSource.getBrowseServiceComponentName();
+ if (preferredComps.containsKey(srcComp)) {
+ // Record the source in the preferred array...
+ preferredSources[preferredComps.get(srcComp)] = mediaSource;
+ // And exclude it from the alpha sort.
+ return false;
+ }
return true;
})
.sorted(Comparator.comparing(
mediaSource -> mediaSource.getDisplayName().toString()))
.collect(Collectors.toList());
+
+ // Concatenate the non null preferred sources and the sorted ones into the result.
+ mMediaSources = new ArrayList<>(sortedSources.size() + preferredFlats.length);
+ Arrays.stream(preferredSources).filter(Objects::nonNull).forEach(mMediaSources::add);
+ mMediaSources.addAll(sortedSources);
}
return mMediaSources;
}
diff --git a/car-ui-lib/generate_rros.mk b/car-ui-lib/generate_rros.mk
index 4e7931a..7e93c36 100644
--- a/car-ui-lib/generate_rros.mk
+++ b/car-ui-lib/generate_rros.mk
@@ -14,7 +14,7 @@
# limitations under the License.
#
-# Generates one RRO for a given package
+# Generates one RRO for a given package.
# $(1) target package name
# $(2) name of the RRO set (e.g. "base")
# $(3) resources folder
@@ -23,8 +23,8 @@
rro_package_name := $(2)-$(subst .,-,$(1))
LOCAL_RESOURCE_DIR := $(3)
+ LOCAL_RRO_THEME := $$(rro_package_name)
LOCAL_PACKAGE_NAME := $$(rro_package_name)
- LOCAL_PRODUCT_MODULE := true
LOCAL_CERTIFICATE := platform
LOCAL_SDK_VERSION := current
diff --git a/car-ui-lib/res/layout-port/car_ui_toolbar.xml b/car-ui-lib/res/layout-port/car_ui_toolbar.xml
index aedecc8..1eb6758 100644
--- a/car-ui-lib/res/layout-port/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout-port/car_ui_toolbar.xml
@@ -20,6 +20,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+ clearFocus() will actually send the focus to the first focusable thing in the layout.
+ If that focusable thing is still the search bar it will just reselect it, and the user won't
+ be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+ <View
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ android:focusable="true"
+ android:focusableInTouchMode="true"/>
+
<androidx.constraintlayout.widget.Guideline
android:id="@+id/row_separator_guideline"
android:layout_width="0dp"
diff --git a/car-ui-lib/res/layout/car_ui_preference_fragment.xml b/car-ui-lib/res/layout/car_ui_preference_fragment.xml
index b70bfa5..766196d 100644
--- a/car-ui-lib/res/layout/car_ui_preference_fragment.xml
+++ b/car-ui-lib/res/layout/car_ui_preference_fragment.xml
@@ -15,10 +15,18 @@
limitations under the License.
-->
-<FrameLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.car.ui.toolbar.Toolbar
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/toolbar"
+ app:state="subpage"/>
<FrameLayout
android:id="@android:id/list_container"
@@ -31,4 +39,4 @@
android:scrollbars="none"/>
</FrameLayout>
-</FrameLayout>
+</LinearLayout>
diff --git a/car-ui-lib/res/layout/car_ui_preference_widget_switch.xml b/car-ui-lib/res/layout/car_ui_preference_widget_switch.xml
index 9ec6697..f610a63 100644
--- a/car-ui-lib/res/layout/car_ui_preference_widget_switch.xml
+++ b/car-ui-lib/res/layout/car_ui_preference_widget_switch.xml
@@ -15,11 +15,16 @@
limitations under the License.
-->
-<Switch
+<com.android.car.ui.preference.PreferenceSwitchWidget
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@null"
- android:clickable="false"
- android:focusable="false"/>
+ android:background="@drawable/car_ui_switch_preference_background_drawable"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:showText="@bool/car_ui_preference_switch_toggle_show_text"
+ android:textColor="@color/car_ui_preference_switch_text_color"
+ android:textOff="@string/car_ui_switch_preference_off"
+ android:textOn="@string/car_ui_switch_preference_on"
+ android:thumb="@drawable/car_ui_switch_preference_toggle_thumb_drawable"
+ android:track="@drawable/car_ui_switch_preference_toggle_track_drawable"/>
diff --git a/car-ui-lib/res/layout/car_ui_search_view.xml b/car-ui-lib/res/layout/car_ui_search_view.xml
index cdfa4b9..b328378 100644
--- a/car-ui-lib/res/layout/car_ui_search_view.xml
+++ b/car-ui-lib/res/layout/car_ui_search_view.xml
@@ -38,12 +38,13 @@
android:id="@+id/search_bar"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:paddingLeft="@dimen/car_ui_touch_target_width"
+ android:paddingStart="@dimen/car_ui_touch_target_width"
+ android:paddingEnd="@dimen/car_ui_touch_target_width"
android:hint="@string/car_ui_toolbar_default_search_hint"
android:textColorHint="@color/car_ui_toolbar_search_hint_text_color"
android:inputType="text"
android:singleLine="true"
- android:imeOptions="actionDone"
+ android:imeOptions="actionSearch"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index e3bcb73..7ee939f 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -20,14 +20,51 @@
android:layout_width="match_parent"
android:layout_height="@dimen/car_ui_toolbar_first_row_height">
+ <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+ clearFocus() will actually send the focus to the first focusable thing in the layout.
+ If that focusable thing is still the search bar it will just reselect it, and the user won't
+ be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+ <View
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ android:focusable="true"
+ android:focusableInTouchMode="true"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/toolbar_start_guideline"
+ app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_start_inset"
+ android:orientation="vertical"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/toolbar_top_guideline"
+ app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_top_inset"
+ android:orientation="horizontal"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/toolbar_end_guideline"
+ app:layout_constraintGuide_end="@dimen/car_ui_toolbar_end_inset"
+ android:orientation="vertical"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/toolbar_bottom_guideline"
+ app:layout_constraintGuide_end="@dimen/car_ui_toolbar_bottom_inset"
+ android:orientation="horizontal"/>
<FrameLayout
android:id="@+id/nav_icon_container"
android:layout_width="@dimen/car_ui_toolbar_nav_button_width"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
- app:layout_constraintStart_toStartOf="parent">
+ app:layout_constraintStart_toStartOf="@id/toolbar_start_guideline">
<ImageView
android:id="@+id/nav_icon"
android:layout_width="@dimen/car_ui_toolbar_icon_size"
@@ -49,7 +86,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/car_ui_toolbar_title_margin_start"
style="@style/TextAppearance.CarUi.Widget.Toolbar.Title"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
app:layout_constraintStart_toEndOf="@+id/nav_icon_container"
app:layout_constraintEnd_toStartOf="@+id/menu_items_container"/>
@@ -59,7 +96,7 @@
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginEnd="@dimen/car_ui_toolbar_menu_item_margin"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
app:layout_constraintStart_toEndOf="@+id/nav_icon_container"
app:layout_constraintEnd_toStartOf="@+id/menu_items_container"
@@ -70,7 +107,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
app:layout_constraintStart_toEndOf="@+id/nav_icon_container"
app:layout_constraintEnd_toStartOf="@+id/menu_items_container"/>
@@ -82,7 +119,7 @@
android:orientation="horizontal"
android:divider="@drawable/car_ui_toolbar_menu_item_divider"
android:showDividers="beginning|middle|end"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_overflow_button"/>
@@ -93,9 +130,9 @@
android:layout_marginLeft="@dimen/car_ui_toolbar_menu_item_margin"
android:layout_marginRight="@dimen/car_ui_toolbar_menu_item_margin"
android:visibility="gone"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@id/bottom_styleable"
- app:layout_constraintEnd_toEndOf="parent">
+ app:layout_constraintEnd_toEndOf="@id/toolbar_end_guideline">
<ImageView
android:src="@drawable/car_ui_icon_overflow_menu"
android:layout_width="@dimen/car_ui_primary_icon_size"
@@ -111,7 +148,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@+id/bottom_styleable"
app:layout_constraintStart_toEndOf="@+id/nav_icon_container"
app:layout_constraintEnd_toStartOf="@+id/menu_items_container"/>
@@ -121,8 +158,7 @@
android:id="@+id/bottom_styleable"
android:layout_width="match_parent"
android:layout_height="0.01dp"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/toolbar_bottom_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
-
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/values/bools.xml b/car-ui-lib/res/values/bools.xml
index c7a7f48..906ccab 100644
--- a/car-ui-lib/res/values/bools.xml
+++ b/car-ui-lib/res/values/bools.xml
@@ -28,4 +28,7 @@
<bool name="car_ui_preference_allow_divider_below">false</bool>
<bool name="car_ui_preference_category_allow_divider_above">false</bool>
<bool name="car_ui_preference_category_allow_divider_below">false</bool>
+
+ <bool name="car_ui_preference_switch_toggle_show_text">false</bool>
+ <bool name="car_ui_preference_switch_toggle_show_animation">true</bool>
</resources>
diff --git a/car-ui-lib/res/values/colors.xml b/car-ui-lib/res/values/colors.xml
index 2a92da5..cb748a7 100644
--- a/car-ui-lib/res/values/colors.xml
+++ b/car-ui-lib/res/values/colors.xml
@@ -49,4 +49,7 @@
<color name="car_ui_preference_edit_text_dialog_message_text_color">#ffdadce0</color>
<color name="car_ui_preference_icon_color">@android:color/white</color>
+
+ <color name="car_ui_preference_switch_text_color">#000000</color>
+
</resources>
diff --git a/car-ui-lib/res/values/dimens.xml b/car-ui-lib/res/values/dimens.xml
index eb4b9e1..d92801d 100644
--- a/car-ui-lib/res/values/dimens.xml
+++ b/car-ui-lib/res/values/dimens.xml
@@ -50,6 +50,10 @@
<dimen name="car_ui_toolbar_first_row_height">@dimen/car_ui_toolbar_row_height</dimen>
<dimen name="car_ui_toolbar_second_row_height">@dimen/car_ui_toolbar_row_height</dimen>
<dimen name="car_ui_toolbar_nav_button_width">@dimen/car_ui_margin</dimen>
+ <dimen name="car_ui_toolbar_start_inset">0dp</dimen>
+ <dimen name="car_ui_toolbar_end_inset">0dp</dimen>
+ <dimen name="car_ui_toolbar_top_inset">0dp</dimen>
+ <dimen name="car_ui_toolbar_bottom_inset">0dp</dimen>
<dimen name="car_ui_toolbar_icon_size">@dimen/car_ui_primary_icon_size</dimen>
<dimen name="car_ui_toolbar_title_margin_start">@dimen/car_ui_padding_2</dimen>
<dimen name="car_ui_toolbar_menu_item_margin">@dimen/car_ui_padding_2</dimen>
diff --git a/car-ui-lib/res/values/values.xml b/car-ui-lib/res/values/values.xml
index 9dba256..5d4d22f 100644
--- a/car-ui-lib/res/values/values.xml
+++ b/car-ui-lib/res/values/values.xml
@@ -18,4 +18,8 @@
<resources>
<bool name="car_ui_tab_flexible_layout">false</bool>
<item name="car_ui_tab_item_layout" type="layout">@layout/car_ui_tab_item</item>
+
+ <item name="car_ui_switch_preference_toggle_thumb_drawable" type="drawable">@*android:drawable/switch_thumb_material_anim</item>
+ <item name="car_ui_switch_preference_toggle_track_drawable" type="drawable">@*android:drawable/switch_track_material</item>
+ <item name="car_ui_switch_preference_background_drawable" type="drawable">@null</item>
</resources>
diff --git a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBar.java b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBar.java
index c98d114..3dbb6aa 100644
--- a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBar.java
@@ -29,6 +29,7 @@
import android.widget.ImageView;
import androidx.annotation.IntRange;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
@@ -43,16 +44,19 @@
* been ported from the PLV with minor updates.
*/
class DefaultScrollBar implements ScrollBar {
+
+ @VisibleForTesting
+ int mPaddingStart;
+ @VisibleForTesting
+ int mPaddingEnd;
+
private float mButtonDisabledAlpha;
- private static final String TAG = "DefaultScrollBar";
private PagedSnapHelper mSnapHelper;
private ImageView mUpButton;
private View mScrollView;
private View mScrollThumb;
private ImageView mDownButton;
- private int mPaddingStart;
- private int mPaddingEnd;
private int mSeparatingMargin;
@@ -75,7 +79,7 @@
@ScrollBarPosition int scrollBarPosition,
boolean scrollBarAboveRecyclerView) {
- this.mRecyclerView = rv;
+ mRecyclerView = rv;
LayoutInflater inflater =
(LayoutInflater) rv.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -84,7 +88,7 @@
mScrollView = inflater.inflate(R.layout.car_ui_pagedrecyclerview_scrollbar, parent, false);
mScrollView.setLayoutParams(
- new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
+ new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
Resources res = rv.getContext().getResources();
diff --git a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerView.java b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerView.java
index 52bc541..adcd526 100644
--- a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerView.java
+++ b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerView.java
@@ -28,12 +28,12 @@
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -45,6 +45,7 @@
import com.android.car.ui.pagedrecyclerview.decorations.linear.LinearOffsetItemDecoration;
import com.android.car.ui.pagedrecyclerview.decorations.linear.LinearOffsetItemDecoration.OffsetPosition;
import com.android.car.ui.toolbar.Toolbar;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
import java.lang.annotation.Retention;
@@ -76,7 +77,8 @@
@Gutter
private int mGutter;
private int mGutterSize;
- private RecyclerView mNestedRecyclerView;
+ @VisibleForTesting
+ RecyclerView mNestedRecyclerView;
private Adapter<?> mAdapter;
private ScrollBar mScrollBar;
private int mInitialTopPadding;
@@ -375,7 +377,6 @@
== null) {
return;
}
-
PagedRecyclerView.this.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
initNestedRecyclerView();
@@ -390,10 +391,6 @@
mNestedRecyclerView
.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
- ViewGroup.LayoutParams params =
- getLayoutParams();
- params.height = getMeasuredHeight();
- setLayoutParams(params);
createScrollBarFromConfig();
if (mInitialTopPadding == 0) {
mInitialTopPadding = getPaddingTop();
@@ -452,11 +449,11 @@
return super.getLayoutManager();
}
+ /**
+ * Refer to {@link PagedRecyclerView#getEffectiveLayoutManager()} for usage in applications.
+ */
@Override
public LayoutManager getLayoutManager() {
- if (mScrollBarEnabled) {
- return mNestedRecyclerView.getLayoutManager();
- }
return super.getLayoutManager();
}
diff --git a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScroller.java b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScroller.java
index 1b73b7a..1cb421d 100644
--- a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScroller.java
+++ b/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScroller.java
@@ -20,12 +20,12 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.ui.R;
import com.android.car.ui.utils.ResourceUtils;
-
/**
* Code drop from {androidx.car.widget.PagedSmoothScroller}
*
@@ -37,12 +37,16 @@
* </ul>
*/
public class PagedSmoothScroller extends LinearSmoothScroller {
- private float mMillisecondsPerInch;
- private float mDecelerationTimeDivisor;
- private float mMillisecondsPerPixel;
-
- private Interpolator mInterpolator;
- private int mDensityDpi;
+ @VisibleForTesting
+ float mMillisecondsPerInch;
+ @VisibleForTesting
+ float mDecelerationTimeDivisor;
+ @VisibleForTesting
+ float mMillisecondsPerPixel;
+ @VisibleForTesting
+ Interpolator mInterpolator;
+ @VisibleForTesting
+ int mDensityDpi;
public PagedSmoothScroller(Context context) {
super(context);
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceSwitchWidget.java b/car-ui-lib/src/com/android/car/ui/preference/PreferenceSwitchWidget.java
new file mode 100644
index 0000000..39ad79a
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/PreferenceSwitchWidget.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.car.ui.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Switch;
+
+import com.android.car.ui.R;
+
+/**
+ * Switch preference widget. This widget is exactly similar to switch widget just that it calls
+ * {@link Switch#jumpDrawablesToCurrentState} on each click.
+ */
+public class PreferenceSwitchWidget extends Switch {
+
+ private Context mContext;
+
+ public PreferenceSwitchWidget(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ public PreferenceSwitchWidget(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ }
+
+ public PreferenceSwitchWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContext = context;
+ }
+
+ public PreferenceSwitchWidget(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mContext = context;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ super.setChecked(checked);
+
+ if (mContext == null) {
+ return;
+ }
+
+ boolean enableAnimation = mContext.getResources().getBoolean(
+ R.bool.car_ui_preference_switch_toggle_show_animation);
+ if (!enableAnimation) {
+ jumpDrawablesToCurrentState();
+ }
+ }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
index ef29354..35898db 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
@@ -19,6 +19,7 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -41,7 +42,22 @@
public class SearchView extends ConstraintLayout {
private final ImageView mIcon;
private final EditText mSearchText;
+ private final View mCloseIcon;
private final Set<Toolbar.OnSearchListener> mListeners = new HashSet<>();
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ onSearch(editable.toString());
+ }
+ };
public SearchView(Context context) {
this(context, null);
@@ -58,9 +74,9 @@
inflater.inflate(R.layout.car_ui_search_view, this, true);
mSearchText = requireViewById(R.id.search_bar);
- View closeIcon = requireViewById(R.id.search_close);
- closeIcon.setOnClickListener(view -> mSearchText.getText().clear());
mIcon = requireViewById(R.id.icon);
+ mCloseIcon = requireViewById(R.id.search_close);
+ mCloseIcon.setOnClickListener(view -> mSearchText.getText().clear());
mSearchText.setOnFocusChangeListener(
(view, hasFocus) -> {
@@ -74,20 +90,8 @@
.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
});
- mSearchText.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- onSearch(editable.toString());
- }
- });
+ mSearchText.addTextChangedListener(mTextWatcher);
mSearchText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE
@@ -98,6 +102,22 @@
});
}
+ @Override
+ public void setVisibility(int visibility) {
+ boolean showing = visibility == View.VISIBLE && getVisibility() != View.VISIBLE;
+
+ super.setVisibility(visibility);
+
+ if (showing) {
+ mSearchText.removeTextChangedListener(mTextWatcher);
+ mSearchText.getText().clear();
+ mSearchText.addTextChangedListener(mTextWatcher);
+ mCloseIcon.setVisibility(View.GONE);
+
+ mSearchText.requestFocus();
+ }
+ }
+
/**
* Adds a listener for the search text changing.
* See also {@link #unregisterOnSearchListener(Toolbar.OnSearchListener)}
@@ -157,6 +177,8 @@
}
private void onSearch(String query) {
+ mCloseIcon.setVisibility(TextUtils.isEmpty(query) ? View.GONE : View.VISIBLE);
+
for (Toolbar.OnSearchListener listener : mListeners) {
listener.onSearch(query);
}
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
index e656aa0..ba92c40 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
@@ -23,11 +23,13 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -60,6 +62,7 @@
public interface OnHeightChangedListener {
/**
* Will be called when the height of the toolbar is changed.
+ *
* @param height new height of the toolbar
*/
void onHeightChanged(int height);
@@ -158,8 +161,6 @@
};
private AlertDialog mOverflowDialog;
-
-
public Toolbar(Context context) {
this(context, null);
}
@@ -262,7 +263,15 @@
}
});
- handleToolbarHeightChangeListeners(getHeight());
+ getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ for (OnHeightChangedListener listener : mOnHeightChangedListeners) {
+ listener.onHeightChanged(getHeight());
+ }
+ }
+ });
}
@Override
@@ -309,35 +318,47 @@
SavedState(Parcel in) {
super(in);
- mTitle = in.readCharSequence();
+ mTitle = readCharSequence(in);
mNavButtonMode = NavButtonMode.valueOf(in.readString());
- mSearchHint = in.readCharSequence();
- mBackgroundShown = in.readBoolean();
- mShowMenuItemsWhileSearching = in.readBoolean();
+ mSearchHint = readCharSequence(in);
+ mBackgroundShown = in.readInt() != 0;
+ mShowMenuItemsWhileSearching = in.readInt() != 0;
mState = State.valueOf(in.readString());
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
- out.writeCharSequence(mTitle);
+ writeCharSequence(out, mTitle);
out.writeString(mNavButtonMode.name());
- out.writeCharSequence(mSearchHint);
- out.writeBoolean(mBackgroundShown);
- out.writeBoolean(mShowMenuItemsWhileSearching);
+ writeCharSequence(out, mSearchHint);
+ out.writeInt(mBackgroundShown ? 1 : 0);
+ out.writeInt(mShowMenuItemsWhileSearching ? 1 : 0);
out.writeString(mState.name());
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
+ @Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
+ @Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
+
+ /** Replacement of hidden Parcel#readCharSequence(Parcel) */
+ private static CharSequence readCharSequence(Parcel in) {
+ return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ /** Replacement of hidden Parcel#writeCharSequence(Parcel, CharSequence) */
+ private static void writeCharSequence(Parcel dest, CharSequence val) {
+ TextUtils.writeToParcel(val, dest, 0);
+ }
}
/**
@@ -642,8 +663,8 @@
mNavIconContainer.setOnClickListener(state != State.HOME ? backClickListener : null);
mNavIconContainer.setClickable(state != State.HOME);
boolean hasTabs = mTabLayout.getTabCount() > 0;
- boolean showTitle = state == State.SUBPAGE || state == State.HOME
- && (!mTitleAndTabsAreMutuallyExclusive || !hasTabs);
+ boolean showTitle = state == State.SUBPAGE
+ || (state == State.HOME && (!mTitleAndTabsAreMutuallyExclusive || !hasTabs));
mTitle.setVisibility(showTitle ? VISIBLE : GONE);
mTabLayout.setVisibility(state == State.HOME && hasTabs ? VISIBLE : GONE);
mSearchView.setVisibility(state == State.SEARCH ? VISIBLE : GONE);
diff --git a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/CarUxRestrictionsUtil.java b/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
similarity index 93%
rename from car-ui-lib/src/com/android/car/ui/pagedrecyclerview/CarUxRestrictionsUtil.java
rename to car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
index dd74116..355050a 100644
--- a/car-ui-lib/src/com/android/car/ui/pagedrecyclerview/CarUxRestrictionsUtil.java
+++ b/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.ui.pagedrecyclerview;
+package com.android.car.ui.utils;
import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_LIMIT_STRING_LENGTH;
@@ -56,13 +56,13 @@
CarUxRestrictionsManager.OnUxRestrictionsChangedListener listener =
(carUxRestrictions) -> {
if (carUxRestrictions == null) {
- this.mCarUxRestrictions = getDefaultRestrictions();
+ mCarUxRestrictions = getDefaultRestrictions();
} else {
- this.mCarUxRestrictions = carUxRestrictions;
+ mCarUxRestrictions = carUxRestrictions;
}
for (OnUxRestrictionsChangedListener observer : mObservers) {
- observer.onRestrictionsChanged(this.mCarUxRestrictions);
+ observer.onRestrictionsChanged(mCarUxRestrictions);
}
};
@@ -121,7 +121,7 @@
}
/**
- * Returns whether any of the given flags is blocked by the current restrictions. If null is
+ * Returns whether any of the given flags are blocked by the specified restrictions. If null is
* given, the method returns true for safety.
*/
public static boolean isRestricted(
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml b/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
index b79960e..23d42ca 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
@@ -21,7 +21,7 @@
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
- android:theme="@style/CarUiTheme">
+ android:theme="@style/Theme.CarUi">
<activity
android:name=".MainActivity"
android:exported="true">
@@ -32,8 +32,26 @@
</activity>
<activity
- android:name=".DialogsActivity"
+ android:name=".dialogs.DialogsActivity"
android:exported="false"
android:parentActivityName=".MainActivity"/>
+ <activity
+ android:name=".pagedrecyclerview.PagedRecyclerViewActivity"
+ android:exported="false"
+ android:parentActivityName=".MainActivity"/>
+ <activity
+ android:name=".pagedrecyclerview.GridPagedRecyclerViewActivity"
+ android:exported="false"
+ android:parentActivityName=".MainActivity"/>
+ <activity
+ android:name=".preferences.PreferenceActivity"
+ android:exported="false"
+ android:parentActivityName=".MainActivity"/>
+ <activity
+ android:name=".toolbar.ToolbarActivity"
+ android:exported="false"
+ android:parentActivityName=".MainActivity">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ </activity>
</application>
</manifest>
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest.xml b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
index 8b3cce7..7d5e392 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
@@ -54,6 +54,8 @@
<activity
android:name=".toolbar.ToolbarActivity"
android:exported="false"
- android:parentActivityName=".MainActivity"/>
+ android:parentActivityName=".MainActivity">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ </activity>
</application>
</manifest>
diff --git a/car-ui-lib/tests/paintbooth/gradlew b/car-ui-lib/tests/paintbooth/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/car-ui-lib/tests/paintbooth/gradlew.bat b/car-ui-lib/tests/paintbooth/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
index 9c7cf1a..88fb060 100644
--- a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
+++ b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
@@ -37,7 +37,7 @@
android:title="@string/title_checkbox_preference"
android:summary="@string/summary_checkbox_preference"/>
- <SwitchPreferenceCompat
+ <SwitchPreference
android:key="switch"
android:title="@string/title_switch_preference"
android:summary="@string/summary_switch_preference"/>
@@ -101,18 +101,18 @@
</Preference>
- <SwitchPreferenceCompat
+ <SwitchPreference
android:key="parent"
android:title="@string/title_parent_preference"
android:summary="@string/summary_parent_preference"/>
- <SwitchPreferenceCompat
+ <SwitchPreference
android:key="child"
android:dependency="parent"
android:title="@string/title_child_preference"
android:summary="@string/summary_child_preference"/>
- <SwitchPreferenceCompat
+ <SwitchPreference
android:key="toggle_summary"
android:title="@string/title_toggle_summary_preference"
android:summaryOn="@string/summary_on_toggle_summary_preference"
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index 531c3a8..e2d3346 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -19,6 +19,7 @@
import android.app.AlertDialog;
import android.os.Bundle;
import android.text.InputType;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -27,7 +28,6 @@
import android.widget.Toast;
import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
import com.android.car.ui.pagedrecyclerview.PagedRecyclerView;
import com.android.car.ui.paintbooth.R;
@@ -91,7 +91,7 @@
new AlertDialog.Builder(this)
.setView(textBox)
.setTitle("Enter the index of the MenuItem to toggle")
- .setPositiveButton("Ok", ((dialog, which) -> {
+ .setPositiveButton("Ok", (dialog, which) -> {
try {
MenuItem item = mMenuItems.get(
Integer.parseInt(textBox.getText().toString()));
@@ -102,7 +102,7 @@
+ "\", valid range is 0 to " + (mMenuItems.size() - 1),
Toast.LENGTH_LONG).show();
}
- }))
+ })
.show();
}));
@@ -161,7 +161,7 @@
}
private PagedRecyclerView.Adapter mAdapter = new PagedRecyclerView.Adapter() {
-
+ @Override
public int getItemCount() {
return mButtons.size();
}
diff --git a/car-ui-lib/tests/robotests/Android.mk b/car-ui-lib/tests/robotests/Android.mk
new file mode 100644
index 0000000..b924900
--- /dev/null
+++ b/car-ui-lib/tests/robotests/Android.mk
@@ -0,0 +1,92 @@
+#
+# 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)
+
+############################################################
+# CarUi lib just for Robolectric test target. #
+############################################################
+include $(CLEAR_VARS)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+ $(LOCAL_PATH)/tests/robotests/res \
+
+LOCAL_PACKAGE_NAME := CarUi
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_JAVA_LIBRARIES := android.car
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ car-ui-lib
+
+include $(BUILD_PACKAGE)
+
+################################################
+# Car Ui Robolectric test target. #
+################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CarUiRoboTests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := config
+
+# Include the testing libraries
+LOCAL_JAVA_LIBRARIES := \
+ android.car \
+ robolectric_android-all-stub \
+ Robolectric_all-target \
+ mockito-robolectric-prebuilt \
+ testng \
+ truth-prebuilt
+
+
+LOCAL_INSTRUMENTATION_FOR := CarUi
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+##################################################################
+# Car Ui runner target to run the previous target. #
+##################################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunCarUiRoboTests
+
+LOCAL_JAVA_LIBRARIES := \
+ android.car \
+ CarUiRoboTests \
+ robolectric_android-all-stub \
+ Robolectric_all-target \
+ mockito-robolectric-prebuilt \
+ testng \
+ truth-prebuilt
+
+LOCAL_TEST_PACKAGE := CarUi
+
+LOCAL_ROBOTEST_FILES := $(filter-out %/BaseRobolectricTest.java,\
+ $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.))
+
+LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
+
+include external/robolectric-shadows/run_robotests.mk
diff --git a/car-ui-lib/tests/robotests/AndroidManifest.xml b/car-ui-lib/tests/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..64926b8
--- /dev/null
+++ b/car-ui-lib/tests/robotests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 Google Inc.
+
+ 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"
+ package="com.android.car.ui.robotests">
+</manifest>
diff --git a/car-ui-lib/tests/robotests/config/robolectric.properties b/car-ui-lib/tests/robotests/config/robolectric.properties
new file mode 100644
index 0000000..8768f6b
--- /dev/null
+++ b/car-ui-lib/tests/robotests/config/robolectric.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2019 Google Inc.
+#
+# 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=packages/apps/Car/libs/car-ui-lib/tests/robotests/AndroidManifest.xml
+sdk=NEWEST_SDK
diff --git a/car-ui-lib/tests/robotests/res/layout/test_grid_paged_recycler_view.xml b/car-ui-lib/tests/robotests/res/layout/test_grid_paged_recycler_view.xml
new file mode 100644
index 0000000..92e16df
--- /dev/null
+++ b/car-ui-lib/tests/robotests/res/layout/test_grid_paged_recycler_view.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.car.ui.pagedrecyclerview.PagedRecyclerView
+ android:id="@+id/test_prv"
+ app:layoutStyle="grid"
+ app:numOfColumns="4"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/car-ui-lib/tests/robotests/res/layout/test_linear_paged_recycler_view.xml b/car-ui-lib/tests/robotests/res/layout/test_linear_paged_recycler_view.xml
new file mode 100644
index 0000000..f2c9416
--- /dev/null
+++ b/car-ui-lib/tests/robotests/res/layout/test_linear_paged_recycler_view.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.car.ui.pagedrecyclerview.PagedRecyclerView
+ android:id="@+id/test_prv"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/CarUiRobolectricTestRunner.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/CarUiRobolectricTestRunner.java
new file mode 100644
index 0000000..7070a88
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/CarUiRobolectricTestRunner.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.car.ui;
+
+import androidx.annotation.NonNull;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.manifest.AndroidManifest;
+import org.robolectric.res.Fs;
+import org.robolectric.res.ResourcePath;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom test runner for CarUi. This is needed because the default behavior for
+ * robolectric is just to grab the resource directory in the target package.
+ * We want to override this to add several spanning different projects.
+ */
+public class CarUiRobolectricTestRunner extends RobolectricTestRunner {
+ private static final Map<String, String> AAR_VERSIONS;
+ private static final String SUPPORT_RESOURCE_PATH_TEMPLATE =
+ "jar:file:prebuilts/sdk/current/androidx/m2repository/androidx/"
+ + "%1$s/%1$s/%2$s/%1$s-%2$s.aar!/res";
+ // contraint-layout aar lives in separate path.
+ // Note its path contains a hyphen.
+ private static final String CONSTRAINT_LAYOUT_RESOURCE_PATH_TEMPLATE =
+ "jar:file:prebuilts/sdk/current/extras/constraint-layout-x/"
+ + "%1$s/%2$s/%1$s-%2$s.aar!/res";
+
+ static {
+ AAR_VERSIONS = new HashMap<>();
+ AAR_VERSIONS.put("appcompat", "1.1.0-alpha01");
+ AAR_VERSIONS.put("constraintlayout", "1.1.2");
+ AAR_VERSIONS.put("preference", "1.1.0-alpha02");
+ }
+
+ public CarUiRobolectricTestRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ private static ResourcePath createResourcePath(@NonNull String filePath) {
+ try {
+ return new ResourcePath(null, Fs.fromURL(new URL(filePath)), null);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("CarUiRobolectricTestRunner failure", e);
+ }
+ }
+
+ /**
+ * Create the resource path for a support library component's JAR.
+ */
+ private static String createSupportResourcePathFromJar(@NonNull String componentId) {
+ if (!AAR_VERSIONS.containsKey(componentId)) {
+ throw new IllegalArgumentException("Unknown component " + componentId
+ + ". Update test with appropriate component name and version.");
+ }
+ if (componentId.equals("constraintlayout")) {
+ return String.format(CONSTRAINT_LAYOUT_RESOURCE_PATH_TEMPLATE, componentId,
+ AAR_VERSIONS.get(componentId));
+ }
+ return String.format(SUPPORT_RESOURCE_PATH_TEMPLATE, componentId,
+ AAR_VERSIONS.get(componentId));
+ }
+
+ /**
+ * We modify the AndroidManifest such that we can add required resources.
+ */
+ @Override
+ protected AndroidManifest getAppManifest(Config config) {
+ try {
+ // Using the manifest file's relative path, we can figure out the application directory.
+ URL appRoot = new URL("file:packages/apps/Car/libs/car-ui-lib/");
+ URL manifestPath = new URL(appRoot, "AndroidManifest.xml");
+ URL resDir = new URL(appRoot, "tests/robotests/res");
+ URL assetsDir = new URL(appRoot, config.assetDir());
+
+ // By adding any resources from libraries we need to the AndroidManifest, we can access
+ // them from within the parallel universe's resource loader.
+ return new AndroidManifest(Fs.fromURL(manifestPath), Fs.fromURL(resDir),
+ Fs.fromURL(assetsDir)) {
+ @Override
+ public List<ResourcePath> getIncludedResourcePaths() {
+ List<ResourcePath> paths = super.getIncludedResourcePaths();
+ paths.add(createResourcePath("file:packages/apps/Car/libs/car-ui-lib/res"));
+
+ // Support library resources. These need to point to the prebuilts of support
+ // library and not the source.
+ paths.add(createResourcePath(createSupportResourcePathFromJar("appcompat")));
+ paths.add(createResourcePath(
+ createSupportResourcePathFromJar("constraintlayout")));
+ paths.add(createResourcePath(createSupportResourcePathFromJar("preference")));
+
+ return paths;
+ }
+ };
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("CarUiRobolectricTestRunner failure", e);
+ }
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/TestConfig.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/TestConfig.java
new file mode 100644
index 0000000..46a9d0c
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/TestConfig.java
@@ -0,0 +1,23 @@
+/*
+ * 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.car.ui;
+
+public class TestConfig {
+ public static final int SDK_VERSION = 28;
+ public static final String MANIFEST_PATH =
+ "packages/apps/Car/car-ui-lib/AndroidManifest.xml";
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBarTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBarTest.java
new file mode 100644
index 0000000..dded5c6
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/DefaultScrollBarTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 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.car.ui.pagedrecyclerview;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.CarUiRobolectricTestRunner;
+import com.android.car.ui.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarUiRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultScrollBarTest {
+
+ private Context mContext;
+ private ScrollBar mScrollBar;
+
+ @Mock
+ private RecyclerView mRecyclerView;
+ @Mock
+ private FrameLayout mParent;
+ @Mock
+ private FrameLayout.LayoutParams mLayoutParams;
+ @Mock
+ private RecyclerView.RecycledViewPool mRecycledViewPool;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mScrollBar = new DefaultScrollBar();
+ }
+
+ @Test
+ public void initialize_shouldInitializeScrollListener() {
+ when(mRecyclerView.getContext()).thenReturn(mContext);
+ when(mRecyclerView.getParent()).thenReturn(mParent);
+ when(mRecyclerView.getRecycledViewPool()).thenReturn(mRecycledViewPool);
+ when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
+
+ mScrollBar.initialize(mRecyclerView, 10, PagedRecyclerView.ScrollBarPosition.START, true);
+
+ // called once in DefaultScrollBar and once in SnapHelper while setting up the call backs
+ // when we use attachToRecyclerView(recyclerview)
+ verify(mRecyclerView, times(2)).addOnScrollListener(
+ any(RecyclerView.OnScrollListener.class));
+ }
+
+ @Test
+ public void initialize_shouldSetMaxRecyclerViews() {
+ when(mRecyclerView.getContext()).thenReturn(mContext);
+ when(mRecyclerView.getParent()).thenReturn(mParent);
+ when(mRecyclerView.getRecycledViewPool()).thenReturn(mRecycledViewPool);
+ when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
+
+ mScrollBar.initialize(mRecyclerView, 10, PagedRecyclerView.ScrollBarPosition.START, true);
+
+ verify(mRecycledViewPool).setMaxRecycledViews(0, 12);
+ }
+
+ @Test
+ public void initialize_shouldNotHaveFlingListener() {
+ when(mRecyclerView.getContext()).thenReturn(mContext);
+ when(mRecyclerView.getParent()).thenReturn(mParent);
+ when(mRecyclerView.getRecycledViewPool()).thenReturn(mRecycledViewPool);
+ when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
+
+ mScrollBar.initialize(mRecyclerView, 10, PagedRecyclerView.ScrollBarPosition.START, true);
+
+ verify(mRecyclerView).setOnFlingListener(null);
+ }
+
+ @Test
+ public void setPadding_shouldSetStartAndEndPadding() {
+ when(mRecyclerView.getContext()).thenReturn(mContext);
+ when(mRecyclerView.getParent()).thenReturn(mParent);
+ when(mRecyclerView.getRecycledViewPool()).thenReturn(mRecycledViewPool);
+ when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
+
+ mScrollBar.initialize(mRecyclerView, 10, PagedRecyclerView.ScrollBarPosition.START, true);
+ mScrollBar.setPadding(10, 20);
+
+ DefaultScrollBar defaultScrollBar = (DefaultScrollBar) mScrollBar;
+
+ assertThat(defaultScrollBar.mPaddingStart).isEqualTo(10);
+ assertThat(defaultScrollBar.mPaddingEnd).isEqualTo(20);
+ }
+
+ @Test
+ public void setPadding_shouldThrowErrorWithoutInitialization() {
+ assertThrows(NullPointerException.class, () -> mScrollBar.setPadding(10, 20));
+ }
+
+ @Test
+ public void requestLayout_shouldThrowErrorWithoutInitialization() {
+ assertThrows(NullPointerException.class, () -> mScrollBar.requestLayout());
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewAdapterTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewAdapterTest.java
new file mode 100644
index 0000000..93e90df
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewAdapterTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.car.ui.pagedrecyclerview;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.ViewGroup;
+
+import com.android.car.ui.CarUiRobolectricTestRunner;
+import com.android.car.ui.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarUiRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PagedRecyclerViewAdapterTest {
+
+ private Context mContext;
+ private PagedRecyclerViewAdapter mPagedRecyclerViewAdapter;
+
+ @Mock
+ private ViewGroup mParent;
+ @Mock
+ private ViewGroup.LayoutParams mLayoutParams;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mPagedRecyclerViewAdapter = new PagedRecyclerViewAdapter();
+ }
+
+ @Test
+ public void getItemCount_shouldAlwaysBeOne() {
+ assertThat(mPagedRecyclerViewAdapter.getItemCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void onCreateViewHolder_frameLayoutNotNull() {
+
+ when(mParent.getContext()).thenReturn(mContext);
+ when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
+
+ PagedRecyclerViewAdapter.NestedRowViewHolder nestedRowViewHolder =
+ mPagedRecyclerViewAdapter.onCreateViewHolder(mParent, 0);
+
+ assertThat(nestedRowViewHolder.frameLayout).isNotNull();
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewTest.java
new file mode 100644
index 0000000..b4243b6
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedRecyclerViewTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 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.car.ui.pagedrecyclerview;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.CarUiRobolectricTestRunner;
+import com.android.car.ui.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarUiRobolectricTestRunner.class)
+public class PagedRecyclerViewTest {
+
+ private Context mContext;
+ private View mView;
+ private PagedRecyclerView mPagedRecyclerView;
+
+ @Mock
+ private RecyclerView.Adapter mAdapter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void onHeightChanged_shouldAddTheValueToInitialTopValue() {
+ mView = LayoutInflater.from(mContext)
+ .inflate(R.layout.test_linear_paged_recycler_view, null);
+
+ mPagedRecyclerView = mView.findViewById(R.id.test_prv);
+
+ assertThat(mPagedRecyclerView.getPaddingBottom()).isEqualTo(0);
+ assertThat(mPagedRecyclerView.getPaddingTop()).isEqualTo(0);
+ assertThat(mPagedRecyclerView.getPaddingStart()).isEqualTo(0);
+ assertThat(mPagedRecyclerView.getPaddingEnd()).isEqualTo(0);
+
+ mPagedRecyclerView.onHeightChanged(10);
+
+ assertThat(mPagedRecyclerView.getPaddingTop()).isEqualTo(10);
+ assertThat(mPagedRecyclerView.getPaddingBottom()).isEqualTo(0);
+ assertThat(mPagedRecyclerView.getPaddingStart()).isEqualTo(0);
+ assertThat(mPagedRecyclerView.getPaddingEnd()).isEqualTo(0);
+ }
+
+ @Test
+ public void setAdapter_shouldInitializeLinearLayoutManager() {
+ mView = LayoutInflater.from(mContext)
+ .inflate(R.layout.test_linear_paged_recycler_view, null);
+
+ mPagedRecyclerView = mView.findViewById(R.id.test_prv);
+ mPagedRecyclerView.setAdapter(mAdapter);
+
+ assertThat(mPagedRecyclerView.getLayoutManager()).isInstanceOf(LinearLayoutManager.class);
+ }
+
+ @Test
+ public void setAdapter_shouldInitializeGridLayoutManager() {
+ mView = LayoutInflater.from(mContext)
+ .inflate(R.layout.test_grid_paged_recycler_view, null);
+
+ mPagedRecyclerView = mView.findViewById(R.id.test_prv);
+ mPagedRecyclerView.setAdapter(mAdapter);
+
+ assertThat(mPagedRecyclerView.getLayoutManager()).isInstanceOf(GridLayoutManager.class);
+ }
+
+ @Test
+ public void init_shouldContainNestedRecyclerView() {
+ mView = LayoutInflater.from(mContext)
+ .inflate(R.layout.test_grid_paged_recycler_view, null);
+
+ mPagedRecyclerView = mView.findViewById(R.id.test_prv);
+
+ assertThat(mPagedRecyclerView.mNestedRecyclerView).isNotNull();
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScrollerTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScrollerTest.java
new file mode 100644
index 0000000..126883e
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSmoothScrollerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.car.ui.pagedrecyclerview;
+
+import static androidx.recyclerview.widget.LinearSmoothScroller.SNAP_TO_START;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.car.ui.CarUiRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarUiRobolectricTestRunner.class)
+public class PagedSmoothScrollerTest {
+
+ private Context mContext;
+ private PagedSmoothScroller mPagedSmoothScroller;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mPagedSmoothScroller = new PagedSmoothScroller(mContext);
+ }
+
+ @Test
+ public void calculateTimeForScrolling_shouldInitializeAllValues() {
+ assertThat(mPagedSmoothScroller.mMillisecondsPerInch).isNotEqualTo(0);
+ assertThat(mPagedSmoothScroller.mDecelerationTimeDivisor).isNotEqualTo(0);
+ assertThat(mPagedSmoothScroller.mMillisecondsPerPixel).isNotEqualTo(0);
+ assertThat(mPagedSmoothScroller.mInterpolator).isNotNull();
+ assertThat(mPagedSmoothScroller.mDensityDpi).isNotEqualTo(0);
+ }
+
+ @Test
+ public void getVerticalSnapPreference_shouldReturnSnapToStart() {
+ assertThat(mPagedSmoothScroller.getVerticalSnapPreference()).isEqualTo(SNAP_TO_START);
+ }
+
+ @Test
+ public void calculateTimeForScrolling_shouldReturnMultiplierOfMillisecondsPerPixel() {
+ assertThat(mPagedSmoothScroller.calculateTimeForScrolling(20)).isEqualTo(
+ (int) Math.ceil(Math.abs(20) * mPagedSmoothScroller.mMillisecondsPerPixel));
+ }
+
+ @Test
+ public void calculateTimeForDeceleration_shouldReturnNotBeZero() {
+ assertThat(mPagedSmoothScroller.calculateTimeForDeceleration(20)).isNotEqualTo(0);
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSnapHelperTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSnapHelperTest.java
new file mode 100644
index 0000000..30d68b2
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/pagedrecyclerview/PagedSnapHelperTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 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.car.ui.pagedrecyclerview;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.CarUiRobolectricTestRunner;
+import com.android.car.ui.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarUiRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PagedSnapHelperTest {
+
+ private Context mContext;
+ private PagedSnapHelper mPagedSnapHelper;
+
+ @Mock
+ private RecyclerView mRecyclerView;
+ @Mock
+ private LinearLayoutManager mLayoutManager;
+ @Mock
+ private RecyclerView.Adapter mAdapter;
+ @Mock
+ private View mChild;
+ @Mock
+ private RecyclerView.LayoutParams mLayoutParams;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mPagedSnapHelper = new PagedSnapHelper(mContext);
+
+ when(mRecyclerView.getContext()).thenReturn(mContext);
+ mPagedSnapHelper.attachToRecyclerView(mRecyclerView);
+ }
+
+ @Test
+ public void smoothScrollBy_invalidSnapPosition_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mRecyclerView).smoothScrollBy(0, 10);
+ }
+
+ @Test
+ public void smoothScrollBy_invalidSnapPositionNoItem_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(0);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mRecyclerView).smoothScrollBy(0, 10);
+ }
+
+ @Test
+ public void smoothScrollBy_invalidSnapPositionNoView_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(10);
+ when(mLayoutManager.canScrollVertically()).thenReturn(false);
+ when(mLayoutManager.canScrollHorizontally()).thenReturn(false);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mRecyclerView).smoothScrollBy(0, 10);
+ }
+
+ @Test
+ public void smoothScrollBy_invalidSnapPositionNoVectore_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(10);
+ when(mLayoutManager.canScrollVertically()).thenReturn(true);
+ when(mLayoutManager.getChildCount()).thenReturn(1);
+ when(mChild.getLayoutParams()).thenReturn(mLayoutParams);
+ when(mLayoutManager.getChildAt(0)).thenReturn(mChild);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mRecyclerView).smoothScrollBy(0, 10);
+ }
+
+ @Test
+ public void smoothScrollBy_invalidSnapPositionNoDelta_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(1);
+ when(mLayoutManager.canScrollVertically()).thenReturn(true);
+ when(mLayoutManager.getChildCount()).thenReturn(1);
+ // no delta
+ when(mLayoutManager.getDecoratedBottom(any())).thenReturn(0);
+ when(mChild.getLayoutParams()).thenReturn(mLayoutParams);
+ when(mLayoutManager.getChildAt(0)).thenReturn(mChild);
+
+ PointF vectorForEnd = new PointF(100, 100);
+ when(mLayoutManager.computeScrollVectorForPosition(0)).thenReturn(vectorForEnd);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mRecyclerView).smoothScrollBy(0, 10);
+ }
+
+ @Test
+ public void smoothScrollBy_validSnapPosition_shouldCallRecylerViewSmoothScrollBy() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(1);
+ when(mLayoutManager.canScrollVertically()).thenReturn(true);
+ when(mLayoutManager.getChildCount()).thenReturn(1);
+ // some delta
+ when(mLayoutManager.getDecoratedBottom(any())).thenReturn(10);
+ when(mChild.getLayoutParams()).thenReturn(mLayoutParams);
+ when(mLayoutManager.getChildAt(0)).thenReturn(mChild);
+
+ PointF vectorForEnd = new PointF(100, 100);
+ when(mLayoutManager.computeScrollVectorForPosition(0)).thenReturn(vectorForEnd);
+
+ mPagedSnapHelper.smoothScrollBy(10);
+
+ verify(mLayoutManager).startSmoothScroll(any(RecyclerView.SmoothScroller.class));
+ }
+
+ @Test
+ public void calculateDistanceToFinalSnap_shouldReturnTopMarginDifference() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(1);
+ when(mLayoutManager.canScrollVertically()).thenReturn(true);
+ when(mLayoutManager.getChildCount()).thenReturn(1);
+ // some delta
+ when(mLayoutManager.getDecoratedTop(any())).thenReturn(10);
+ when(mChild.getLayoutParams()).thenReturn(mLayoutParams);
+
+ int[] distance = mPagedSnapHelper.calculateDistanceToFinalSnap(mLayoutManager, mChild);
+
+ assertThat(distance[1]).isEqualTo(10);
+ }
+
+ @Test
+ public void calculateScrollDistance_shouldScrollHeightOfView() {
+ when(mRecyclerView.getLayoutManager()).thenReturn(mLayoutManager);
+ when(mLayoutManager.getItemCount()).thenReturn(1);
+ when(mLayoutManager.canScrollVertically()).thenReturn(true);
+ when(mLayoutManager.getChildCount()).thenReturn(1);
+ // some delta
+ when(mLayoutManager.getDecoratedTop(any())).thenReturn(10);
+ when(mChild.getLayoutParams()).thenReturn(mLayoutParams);
+ when(mLayoutManager.getChildAt(0)).thenReturn(mChild);
+ when(mLayoutManager.getHeight()).thenReturn(-50);
+
+ int[] distance = mPagedSnapHelper.calculateScrollDistance(0, 10);
+
+ assertThat(distance[1]).isEqualTo(50);
+ }
+}
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java
new file mode 100644
index 0000000..fe6f80c
--- /dev/null
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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.car.ui.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.drivingstate.CarUxRestrictions;
+
+import com.android.car.ui.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CarUxRestrictionsUtilTest {
+ private int[] mRestrictionsArray;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRestrictionsArray = new int[]{
+ CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD,
+ CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD,
+ CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD
+ | CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD,
+ CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED
+ };
+ }
+
+ @Test
+ public void testNullActiveRestriction() {
+ CarUxRestrictions activeRestrictions = null;
+ boolean[] expectedResults = {true, true, true, true};
+ for (int i = 0; i < mRestrictionsArray.length; i++) {
+ boolean actualResult = CarUxRestrictionsUtil.isRestricted(mRestrictionsArray[i],
+ activeRestrictions);
+ assertThat(actualResult == expectedResults[i]).isTrue();
+ }
+ }
+
+ @Test
+ public void testOneActiveRestriction() {
+ CarUxRestrictions activeRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */true,
+ CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD, /* timestamp= */0).build();
+ boolean[] expectedResults = {true, false, true, true};
+ for (int i = 0; i < mRestrictionsArray.length; i++) {
+ boolean actualResult = CarUxRestrictionsUtil.isRestricted(mRestrictionsArray[i],
+ activeRestrictions);
+ assertThat(actualResult == expectedResults[i]).isTrue();
+ }
+ }
+
+ @Test
+ public void testMultipleActiveRestrictions() {
+ CarUxRestrictions activeRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */true,
+ CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD
+ | CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE, /* timestamp= */
+ 0).build();
+ boolean[] expectedResults = {true, false, true, true};
+ for (int i = 0; i < mRestrictionsArray.length; i++) {
+ boolean actualResult = CarUxRestrictionsUtil.isRestricted(mRestrictionsArray[i],
+ activeRestrictions);
+ assertThat(actualResult == expectedResults[i]).isTrue();
+ }
+ }
+}