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">&#8230;</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)