Merge "Introduce UX Restrictions compliant ImageView" into pi-car-dev
diff --git a/car-apps-common/Android.mk b/car-apps-common/Android.mk
index c2f54be..ce96f11 100644
--- a/car-apps-common/Android.mk
+++ b/car-apps-common/Android.mk
@@ -49,3 +49,7 @@
androidx-constraintlayout_constraintlayout-solver
include $(BUILD_STATIC_JAVA_LIBRARY)
+
+ifeq (,$(ONE_SHOT_MAKEFILE))
+ include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/car-apps-common/res/color/uxr_image_view_color_selector.xml b/car-apps-common/res/color/uxr_image_view_color_selector.xml
new file mode 100644
index 0000000..cfc4728
--- /dev/null
+++ b/car-apps-common/res/color/uxr_image_view_color_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_ux_restricted="true" android:color="@color/uxr_image_view_disabled_color"/>
+ <item app:state_ux_restricted="false" android:color="@color/primary_app_icon_color"/>
+</selector>
diff --git a/car-apps-common/res/values/attrs.xml b/car-apps-common/res/values/attrs.xml
index 1a9f41a..a6416a1 100644
--- a/car-apps-common/res/values/attrs.xml
+++ b/car-apps-common/res/values/attrs.xml
@@ -90,4 +90,23 @@
<enum name="right" value="2"/>
</attr>
</declare-styleable>
+
+ <!-- Attributes for the UxrImageView. -->
+ <declare-styleable name="UxrImageView">
+ <attr name="carUxRestrictions">
+ <!-- Values are copied from android.car.drivingstate.CarUxRestrictions. Note:
+ UX_RESTRICTIONS_BASELINE is not allowed here because it's useless and confusing. -->
+ <flag name="UX_RESTRICTIONS_NO_DIALPAD" value="1"/>
+ <flag name="UX_RESTRICTIONS_NO_FILTERING" value="2"/>
+ <flag name="UX_RESTRICTIONS_LIMIT_STRING_LENGTH" value="4"/>
+ <flag name="UX_RESTRICTIONS_NO_KEYBOARD" value="8"/>
+ <flag name="UX_RESTRICTIONS_NO_VIDEO" value="16"/>
+ <flag name="UX_RESTRICTIONS_LIMIT_CONTENT" value="32"/>
+ <flag name="UX_RESTRICTIONS_NO_SETUP" value="64"/>
+ <flag name="UX_RESTRICTIONS_NO_TEXT_MESSAGE" value="128"/>
+ <flag name="UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION" value="256"/>
+ <flag name="UX_RESTRICTIONS_FULLY_RESTRICTED" value="511"/>
+ </attr>
+ <attr name="state_ux_restricted" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/car-apps-common/res/values/colors.xml b/car-apps-common/res/values/colors.xml
index a248f20..cc8fae4 100644
--- a/car-apps-common/res/values/colors.xml
+++ b/car-apps-common/res/values/colors.xml
@@ -57,4 +57,6 @@
<color name="car_card_ripple_background">#17000000</color>
<color name="background_image_30p_black">#4D000000</color>
+
+ <color name="uxr_image_view_disabled_color">#80FFFFFF</color>
</resources>
diff --git a/car-apps-common/res/values/strings.xml b/car-apps-common/res/values/strings.xml
index 0c12aa1..b2aaae5 100644
--- a/car-apps-common/res/values/strings.xml
+++ b/car-apps-common/res/values/strings.xml
@@ -23,6 +23,8 @@
<!-- CarUxRestrictions Utility -->
<string name="ellipsis" translatable="false">…</string>
+ <!-- Warn user that the action they are trying to perform is blocked while the car is in motion [CHAR LIMIT=60] -->
+ <string name="restricted_while_driving">Feature not available while driving.</string>
<string name="scroll_bar_page_down_button">Scroll down</string>
<string name="scroll_bar_page_up_button">Scroll up</string>
diff --git a/car-apps-common/src/com/android/car/apps/common/UxrImageView.java b/car-apps-common/src/com/android/car/apps/common/UxrImageView.java
new file mode 100644
index 0000000..8fd6044
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/UxrImageView.java
@@ -0,0 +1,147 @@
+/*
+ * 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.apps.common;
+
+import android.annotation.Nullable;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * UX Restrictions compliant ImageView.
+ * This class will automatically listen to Car UXRestrictions, and respond to click event
+ * accordingly. You can set one or multiple restrictions in the layout file, e.g.,
+ * app:carUxRestrictions="UX_RESTRICTIONS_NO_SETUP|UX_RESTRICTIONS_NO_KEYBOARD"
+ * If not set, it'll use UX_RESTRICTIONS_FULLY_RESTRICTED as fallback.
+ * If no restriction is enforced, this ImageView will work as a normal ImageView; otherwise, its
+ * OnClickListener will be disabled if any, and a blocking message will be displayed.
+ */
+public class UxrImageView extends ImageView {
+ private static final int[] STATE_UX_RESTRICTED = {R.attr.state_ux_restricted};
+
+ private CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+ private CarUxRestrictions mActiveCarUxRestrictions;
+ private View.OnClickListener mOnClickListenerDelegate;
+
+ @CarUxRestrictions.CarUxRestrictionsInfo
+ private int mRestrictions;
+
+ private final Handler mHandler = new Handler();
+
+ private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+ this::updateActiveCarUxRestrictions;
+
+ private final View.OnClickListener mOnClickListenerWrapper = (View v) -> {
+ if (mOnClickListenerDelegate == null) {
+ return;
+ }
+ if (isRestricted()) {
+ showBlockingMessage();
+ } else {
+ mOnClickListenerDelegate.onClick(v);
+ }
+ };
+
+ public UxrImageView(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public UxrImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public UxrImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+ public UxrImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+ super.setOnClickListener(mOnClickListenerWrapper);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UxrImageView);
+ try {
+ mRestrictions = a.getInteger(R.styleable.UxrImageView_carUxRestrictions,
+ CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isRestricted()) {
+ mergeDrawableStates(drawableState, STATE_UX_RESTRICTED);
+ }
+ return drawableState;
+ }
+
+ @Override
+ public void setOnClickListener(@Nullable View.OnClickListener listener) {
+ mOnClickListenerDelegate = listener;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mCarUxRestrictionsUtil.register(mListener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mCarUxRestrictionsUtil.unregister(mListener);
+ }
+
+ @VisibleForTesting
+ boolean isRestricted() {
+ return CarUxRestrictionsUtil.isRestricted(mRestrictions, mActiveCarUxRestrictions);
+ }
+
+ @VisibleForTesting
+ void updateActiveCarUxRestrictions(CarUxRestrictions carUxRestrictions) {
+ mActiveCarUxRestrictions = carUxRestrictions;
+ mHandler.post(() -> refreshDrawableState());
+ }
+
+ /**
+ * Shows a message to inform the user that the current feature is not available when driving.
+ */
+ protected void showBlockingMessage() {
+ Toast.makeText(getContext(), R.string.restricted_while_driving,
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @VisibleForTesting
+ void setRestrictions(@CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
+ mRestrictions = restrictions;
+ }
+}
diff --git a/car-apps-common/tests/Android.mk b/car-apps-common/tests/Android.mk
new file mode 100644
index 0000000..9f0a4e8
--- /dev/null
+++ b/car-apps-common/tests/Android.mk
@@ -0,0 +1,19 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all makefiles in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/car-apps-common/tests/robotests/Android.mk b/car-apps-common/tests/robotests/Android.mk
new file mode 100644
index 0000000..2438678
--- /dev/null
+++ b/car-apps-common/tests/robotests/Android.mk
@@ -0,0 +1,73 @@
+LOCAL_PATH := $(call my-dir)
+
+############################################################
+# CarAppsCommon app just for Robolectric test target. #
+############################################################
+include $(CLEAR_VARS)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := CarAppsCommon
+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-apps-common
+
+include $(BUILD_PACKAGE)
+
+################################################
+# Car Apps Common Robolectric test target. #
+################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CarAppsCommonRoboTests
+
+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 \
+ truth-prebuilt
+
+
+LOCAL_INSTRUMENTATION_FOR := CarAppsCommon
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+##################################################################
+# Car Apps Common runner target to run the previous target. #
+##################################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunCarAppsCommonRoboTests
+
+LOCAL_JAVA_LIBRARIES := \
+ android.car \
+ CarAppsCommonRoboTests \
+ robolectric_android-all-stub \
+ Robolectric_all-target \
+ mockito-robolectric-prebuilt \
+ truth-prebuilt
+
+LOCAL_TEST_PACKAGE := CarAppsCommon
+
+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-apps-common/tests/robotests/AndroidManifest.xml b/car-apps-common/tests/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..81f5739
--- /dev/null
+++ b/car-apps-common/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.apps.common.robotests">
+</manifest>
diff --git a/car-apps-common/tests/robotests/config/robolectric.properties b/car-apps-common/tests/robotests/config/robolectric.properties
new file mode 100644
index 0000000..fa63823
--- /dev/null
+++ b/car-apps-common/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-apps-common/tests/robotests/AndroidManifest.xml
+sdk=NEWEST_SDK
diff --git a/car-apps-common/tests/robotests/src/com/android/car/apps/common/CarUxRestrictionsUtilTest.java b/car-apps-common/tests/robotests/src/com/android/car/apps/common/CarUxRestrictionsUtilTest.java
new file mode 100755
index 0000000..2cadad3
--- /dev/null
+++ b/car-apps-common/tests/robotests/src/com/android/car/apps/common/CarUxRestrictionsUtilTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.apps.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.drivingstate.CarUxRestrictions;
+
+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();
+ }
+ }
+}
diff --git a/car-apps-common/tests/robotests/src/com/android/car/apps/common/TestConfig.java b/car-apps-common/tests/robotests/src/com/android/car/apps/common/TestConfig.java
new file mode 100644
index 0000000..baf9b51
--- /dev/null
+++ b/car-apps-common/tests/robotests/src/com/android/car/apps/common/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.apps.common;
+
+public class TestConfig {
+ public static final int SDK_VERSION = 23;
+ public static final String MANIFEST_PATH =
+ "packages/apps/Car/car-apps-common/AndroidManifest.xml";
+}
diff --git a/car-media-common/tests/robotests/Android.mk b/car-media-common/tests/robotests/Android.mk
index 3dd04f7..342f5c0 100644
--- a/car-media-common/tests/robotests/Android.mk
+++ b/car-media-common/tests/robotests/Android.mk
@@ -1,7 +1,7 @@
LOCAL_PATH := $(call my-dir)
############################################################
-# CarArchCommon app just for Robolectric test target. #
+# CarMediaCommon app just for Robolectric test target. #
############################################################
include $(CLEAR_VARS)