Merge "Add Preference themes to Car overlay."
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index c3a2657..f614601 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -68,6 +68,14 @@
*/
public static final String LAST_ACTIVE_USER_ID =
"android.car.LAST_ACTIVE_USER_ID";
+
+ /**
+ * Whether default restrictions for users have been set.
+ *
+ * @hide
+ */
+ public static final String DEFAULT_USER_RESTRICTIONS_SET =
+ "android.car.DEFAULT_USER_RESTRICTIONS_SET";
}
/**
diff --git a/service/src/com/android/car/hal/InputHalService.java b/service/src/com/android/car/hal/InputHalService.java
index c5174dc..f0993eb 100644
--- a/service/src/com/android/car/hal/InputHalService.java
+++ b/service/src/com/android/car/hal/InputHalService.java
@@ -23,35 +23,59 @@
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.os.SystemClock;
import android.util.Log;
-import android.util.SparseLongArray;
+import android.util.SparseArray;
import android.view.InputDevice;
import android.view.KeyEvent;
import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import java.util.function.LongSupplier;
public class InputHalService extends HalServiceBase {
public static final int DISPLAY_MAIN = VehicleDisplay.MAIN;
public static final int DISPLAY_INSTRUMENT_CLUSTER = VehicleDisplay.INSTRUMENT_CLUSTER;
private final VehicleHal mHal;
+ /** A function to retrieve the current system uptime - replaceable for testing. */
+ private final LongSupplier mUptimeSupplier;
public interface InputListener {
void onKeyEvent(KeyEvent event, int targetDisplay);
}
+ /** The current press state of a key. */
+ private static class KeyState {
+ /** The timestamp (uptimeMillis) of the last ACTION_DOWN event for this key. */
+ public long mLastKeyDownTimestamp = -1;
+ /** The number of ACTION_DOWN events that have been sent for this keypress. */
+ public int mRepeatCount = 0;
+ }
+
private static final boolean DBG = false;
+ @GuardedBy("this")
private boolean mKeyInputSupported = false;
+
+ @GuardedBy("this")
private InputListener mListener;
- private final SparseLongArray mKeyDownTimes = new SparseLongArray();
+
+ @GuardedBy("mKeyStates")
+ private final SparseArray<KeyState> mKeyStates = new SparseArray<>();
public InputHalService(VehicleHal hal) {
+ this(hal, SystemClock::uptimeMillis);
+ }
+
+ @VisibleForTesting
+ InputHalService(VehicleHal hal, LongSupplier uptimeSupplier) {
mHal = hal;
+ mUptimeSupplier = uptimeSupplier;
}
public void setInputListener(InputListener listener) {
@@ -126,23 +150,43 @@
}
private void dispatchKeyEvent(InputListener listener, int action, int code, int display) {
- long eventTime = SystemClock.uptimeMillis();
+ long eventTime = mUptimeSupplier.getAsLong();
- if (action == KeyEvent.ACTION_DOWN) {
- mKeyDownTimes.put(code, eventTime);
+ long downTime;
+ int repeat;
+
+ synchronized (mKeyStates) {
+ KeyState state = mKeyStates.get(code);
+ if (state == null) {
+ state = new KeyState();
+ mKeyStates.put(code, state);
+ }
+
+ if (action == KeyEvent.ACTION_DOWN) {
+ downTime = eventTime;
+ repeat = state.mRepeatCount++;
+ state.mLastKeyDownTimestamp = eventTime;
+ } else {
+ // Handle key up events without any matching down event by setting the down time to
+ // the event time. This shouldn't happen in practice - keys should be pressed
+ // before they can be released! - but this protects us against HAL weirdness.
+ downTime =
+ (state.mLastKeyDownTimestamp == -1)
+ ? eventTime
+ : state.mLastKeyDownTimestamp;
+ repeat = 0;
+ state.mRepeatCount = 0;
+ }
}
- long downTime = action == KeyEvent.ACTION_UP
- ? mKeyDownTimes.get(code, eventTime) : eventTime;
-
KeyEvent event = KeyEvent.obtain(
downTime,
eventTime,
action,
code,
- 0 /* repeat */,
+ repeat,
0 /* meta state */,
- 0 /* deviceId*/,
+ 0 /* deviceId */,
0 /* scancode */,
0 /* flags */,
InputDevice.SOURCE_CLASS_BUTTON,
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 8716aed..72e313f 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -17,6 +17,7 @@
package com.android.car.user;
import android.annotation.Nullable;
+import android.car.settings.CarSettings;
import android.car.userlib.CarUserManagerHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -25,6 +26,7 @@
import android.location.LocationManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.Log;
import com.android.car.CarServiceBase;
@@ -86,10 +88,16 @@
}
if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
- if (mCarUserManagerHelper.getAllUsers().size() == 0) {
+ // We want to set restrictions on system and guest users only once. These are persisted
+ // onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, /* default= */ 0) == 0) {
setSystemUserRestrictions();
mCarUserManagerHelper.initDefaultGuestRestrictions();
+ Settings.Global.putInt(mContext.getContentResolver(),
+ CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
}
+
} else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
// Update last active user if the switched-to user is a persistent, non-system user.
int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
diff --git a/tests/ThemePlayground/Android.mk b/tests/ThemePlayground/Android.mk
new file mode 100644
index 0000000..90d12b0
--- /dev/null
+++ b/tests/ThemePlayground/Android.mk
@@ -0,0 +1,48 @@
+#
+# Copyright (C) 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_STATIC_ANDROID_LIBRARIES += \
+ androidx.car_car \
+ car-theme-lib \
+ androidx.transition_transition \
+ androidx.legacy_legacy-support-v4 \
+ androidx-constraintlayout_constraintlayout
+
+LOCAL_MODULE_OWNER := google
+LOCAL_PACKAGE_NAME := ThemePlayground
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ androidx-constraintlayout_constraintlayout-solver
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ThemePlayground/AndroidManifest.xml b/tests/ThemePlayground/AndroidManifest.xml
new file mode 100644
index 0000000..def394f
--- /dev/null
+++ b/tests/ThemePlayground/AndroidManifest.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.themeplayground">
+ <uses-sdk
+ android:minSdkVersion="26"
+ android:targetSdkVersion="26"/>
+ <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.App">
+ <activity
+ android:name=".TextSamples"
+ android:label="@string/app_name"
+ android:windowSoftInputMode="stateUnchanged"
+ android:resizeableActivity="true"
+ android:allowEmbedded="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ColorSamples"
+ android:label="@string/panel_elements"
+ android:windowSoftInputMode="stateUnchanged"
+ android:resizeableActivity="true"
+ android:allowEmbedded="true">
+ </activity>
+ <activity android:name=".DialogSamples"
+ android:label="@string/dialog_elements"
+ android:parentActivityName="com.android.car.themeplayground.TextSamples">
+ </activity>
+ <activity android:name=".WidgetsSamples"
+ android:label="@string/widgets"
+ android:parentActivityName="com.android.car.themeplayground.TextSamples">
+ </activity>
+ <activity android:name=".PagedListViewSamples"
+ android:label="@string/paged_list_view"
+ android:parentActivityName="com.android.car.themeplayground.TextSamples">
+ </activity>
+ <activity android:name=".DefaultThemeSamples"
+ android:label="@string/default_themes"
+ android:parentActivityName="com.android.car.themeplayground.TextSamples">
+ </activity>
+ <activity android:name=".MultipleIntentSamples"
+ android:label="@string/multiple_intent"
+ android:parentActivityName="com.android.car.themeplayground.TextSamples">
+ </activity>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/drawable/ic_launcher.png b/tests/ThemePlayground/res/drawable/ic_launcher.png
new file mode 100644
index 0000000..5d5dc1a
--- /dev/null
+++ b/tests/ThemePlayground/res/drawable/ic_launcher.png
Binary files differ
diff --git a/tests/ThemePlayground/res/drawable/ic_launcher_background.xml b/tests/ThemePlayground/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..29fd4e0
--- /dev/null
+++ b/tests/ThemePlayground/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillColor="#26A69A"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+</vector>
diff --git a/tests/ThemePlayground/res/drawable/ic_launcher_foreground.xml b/tests/ThemePlayground/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..497b104
--- /dev/null
+++ b/tests/ThemePlayground/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1" />
+</vector>
diff --git a/tests/ThemePlayground/res/layout/device_default_theme_samples.xml b/tests/ThemePlayground/res/layout/device_default_theme_samples.xml
new file mode 100644
index 0000000..94391cd
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/device_default_theme_samples.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="com.codinginflow.radiobuttonexample.MainActivity">
+
+ <TextView
+ android:id="@+id/set_application_theme"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="16dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:text="@string/theme_name"/>
+
+ <AutoCompleteTextView
+ android:id="@+id/theme_name"
+ android:layout_width="800dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/button_apply"
+ app:layout_constraintStart_toEndOf="@+id/set_application_theme"/>
+
+ <Button
+ android:id="@+id/button_apply"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/text_view_selected"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="16dp"
+ android:text="apply"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/theme_name"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/dialog_samples.xml b/tests/ThemePlayground/res/layout/dialog_samples.xml
new file mode 100644
index 0000000..9958de1
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/dialog_samples.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/dialogLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/set_background"
+ android:id="@+id/setBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintBottom_toTopOf="@+id/showDialogBT"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+
+ <Button
+ android:id="@+id/showDialogBT"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show Dialog"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/showDialogWithCheckboxBT"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show Dialog With Checkbox"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/showDialogBT" />
+
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/item_list.xml b/tests/ThemePlayground/res/layout/item_list.xml
new file mode 100644
index 0000000..67b5b63
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/item_list.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:id="@+id/textTitle"
+ android:text="TESTING"/>
+
+</LinearLayout>
diff --git a/tests/ThemePlayground/res/layout/menu_button.xml b/tests/ThemePlayground/res/layout/menu_button.xml
new file mode 100644
index 0000000..9dbe9ee
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/menu_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/dialogLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/button_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/text_view_selected"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="menu"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/multiple_intent_samples.xml b/tests/ThemePlayground/res/layout/multiple_intent_samples.xml
new file mode 100644
index 0000000..fcca86f
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/multiple_intent_samples.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ 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">
+
+ <Button
+ android:id="@+id/fire_web_intent_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Fire Intent web"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/paged_list_view_samples.xml b/tests/ThemePlayground/res/layout/paged_list_view_samples.xml
new file mode 100644
index 0000000..a3ebb1e
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/paged_list_view_samples.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<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:orientation="vertical">
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/list"
+ android:theme="@style/PagedListTheme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:gutter="both" />
+ <include layout="@layout/menu_button"/>
+</LinearLayout>
diff --git a/tests/ThemePlayground/res/layout/panel_samples.xml b/tests/ThemePlayground/res/layout/panel_samples.xml
new file mode 100644
index 0000000..8e19e14
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/panel_samples.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ 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">
+ <ScrollView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorPrimary">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorPrimary"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorPrimaryDark">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorPrimaryDark"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorAccent">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorAccent"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorForeground">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorForeground"
+ android:textColor="?android:attr/textColorPrimaryInverse"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorBackground">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorBackground"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/windowBackground">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/windowBackground"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorBackgroundFloating">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorBackgroundFloating"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorActivatedHighlight">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorActivatedHighlight"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorControlActivated">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorControlActivated"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorControlHighlight">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorControlHighlight"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorControlNormal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorControlNormal"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorError">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorError"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorButtonNormal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorButtonNormal"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/colorFocusedHighlight">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/colorFocusedHighlight"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/panelColorBackground">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/panelColorBackground"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:background="?android:attr/panelColorForeground">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android:attr/panelColorForeground"
+ android:textColor="?android:attr/textColorPrimaryInverse"/>
+ </FrameLayout>
+ </LinearLayout>
+ </ScrollView>
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/set_background.xml b/tests/ThemePlayground/res/layout/set_background.xml
new file mode 100644
index 0000000..7cb86b9
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/set_background.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:orientation="horizontal"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/set_background_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="8dp"
+ android:text="@string/background_name" />
+
+ <EditText
+ android:id="@+id/background_input_color"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:ems="10" />
+
+ <Button
+ android:id="@+id/set_background_color"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="30dp"
+ android:text="@string/apply"/>
+
+ <Button
+ android:id="@+id/reset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="50dp"
+ android:text="@string/reset"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/text_samples.xml b/tests/ThemePlayground/res/layout/text_samples.xml
new file mode 100644
index 0000000..cc806bf
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/text_samples.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 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">
+
+ <include layout="@layout/set_background"
+ android:id="@+id/setBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintBottom_toTopOf="@+id/text_sample_list"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <ScrollView
+ android:id="@+id/text_sample_list"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/setBackground">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/unset"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Text Appearance Not Set (thus textAppearanceSmall)"/>
+
+ <TextView
+ android:id="@+id/textAppearanceLarge"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceLarge"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <TextView
+ android:id="@+id/textAppearanceLargeInverse"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceLargeInverse"
+ android:textAppearance="?android:attr/textAppearanceLargeInverse" />
+
+ <TextView
+ android:id="@+id/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="TextApperanceMedium"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/textAppearanceMediumInverse"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceMediumInverse"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse" />
+
+ <TextView
+ android:id="@+id/textAppearance"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearance"
+ android:textAppearance="?android:attr/textAppearance" />
+
+ <TextView
+ android:id="@+id/textAppearanceInverse"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceInverse"
+ android:textAppearance="?android:attr/textAppearanceInverse" />
+
+ <TextView
+ android:id="@+id/small"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="TextApperanceSmall"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/textAppearanceSmallInverse"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceSmallInverse"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse" />
+
+ <TextView
+ android:id="@+id/textAppearanceListItem"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceListItem"
+ android:textAppearance="?android:attr/textAppearanceListItem" />
+
+ <TextView
+ android:id="@+id/textAppearanceListItemSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceListItemSmall"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall" />
+
+ <TextView
+ android:id="@+id/textAppearanceListItemSecondary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="textAppearanceListItemSecondary"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+ </LinearLayout>
+ </ScrollView>
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/layout/widget_samples.xml b/tests/ThemePlayground/res/layout/widget_samples.xml
new file mode 100644
index 0000000..54472ac
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/widget_samples.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/widgetLayout"
+ android:background="@color/widget_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/set_background"
+ android:id="@+id/setBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintBottom_toTopOf="@+id/widget_checkbox"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="100dp"
+ android:orientation="horizontal"
+ android:id="@+id/checkbox_layout"
+ app:layout_constraintTop_toBottomOf="@+id/setBackground"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <TextView
+ android:id="@+id/widget_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/widget_checkbox"/>
+
+ <CheckBox android:id="@+id/widget_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="100dp"
+ android:id="@+id/switch_layout"
+ app:layout_constraintTop_toBottomOf="@+id/checkbox_layout"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <TextView
+ android:id="@+id/toggle_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/toggle_switch"/>
+
+ <Switch
+ android:id="@+id/widget_switch"
+ android:layout_width="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="100dp"
+ android:id="@+id/progress_bar_layout"
+ app:layout_constraintTop_toBottomOf="@+id/switch_layout"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <TextView
+ android:id="@+id/progress_bar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/progress_bar"/>
+
+ <SeekBar
+ android:id="@+id/widget_seek_bar"
+ android:layout_width="200dp"
+ android:layout_height="30dp"
+ android:layout_marginStart="16dp"/>
+
+ </LinearLayout>
+
+
+ <Button
+ android:id="@+id/trigger_config_change"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Trigger Config change Day/Night Mode"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/progress_bar_layout"/>
+ <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/menu/menu_main.xml b/tests/ThemePlayground/res/menu/menu_main.xml
new file mode 100644
index 0000000..eb7f771
--- /dev/null
+++ b/tests/ThemePlayground/res/menu/menu_main.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.google.themetesting.Main">
+ <item
+ android:id="@+id/text_elements"
+ android:orderInCategory="100"
+ android:title="@string/text_elements"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/panel_elements"
+ android:orderInCategory="100"
+ android:title="@string/panel_elements"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/dialog_elements"
+ android:orderInCategory="100"
+ android:title="@string/dialog_elements"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/toggle_theme"
+ android:orderInCategory="100"
+ android:title="@string/toggle_theme"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/widgets"
+ android:orderInCategory="100"
+ android:title="@string/widgets"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/page_list_view"
+ android:orderInCategory="100"
+ android:title="@string/paged_list_view"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/default_themes"
+ android:orderInCategory="100"
+ android:title="@string/default_themes"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/multiple_intent"
+ android:orderInCategory="100"
+ android:title="@string/multiple_intent"
+ app:showAsAction="never" />
+</menu>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/raw/theme_names.txt b/tests/ThemePlayground/res/raw/theme_names.txt
new file mode 100644
index 0000000..ea3394d
--- /dev/null
+++ b/tests/ThemePlayground/res/raw/theme_names.txt
@@ -0,0 +1,3 @@
+Theme.DeviceDefault
+Theme.DeviceDefault.Light
+Theme.DeviceDefault.NoActionBar
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/values-night/colors.xml b/tests/ThemePlayground/res/values-night/colors.xml
new file mode 100644
index 0000000..036beb3
--- /dev/null
+++ b/tests/ThemePlayground/res/values-night/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <color name="widget_background">#1565c0</color>
+</resources>
diff --git a/tests/ThemePlayground/res/values/colors.xml b/tests/ThemePlayground/res/values/colors.xml
new file mode 100644
index 0000000..421c33c
--- /dev/null
+++ b/tests/ThemePlayground/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <color name="widget_background">#7986CB</color>
+</resources>
diff --git a/tests/ThemePlayground/res/values/strings.xml b/tests/ThemePlayground/res/values/strings.xml
new file mode 100644
index 0000000..3c0b403
--- /dev/null
+++ b/tests/ThemePlayground/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+<resources>
+ <string name="app_name">AAE Theme Playground</string>
+ <string name="text_elements">Text Elements</string>
+ <string name="panel_elements">Color Panels</string>
+ <string name="dialog_elements">Dialogs</string>
+ <string name="toggle_theme">Change configuration(Day/Night)</string>
+ <string name="apply">Apply</string>
+ <string name="widgets">Widgets</string>
+ <string name="paged_list_view">Paged List View</string>
+ <string name="widget_checkbox">Checkbox</string>
+ <string name="toggle_switch">Toggle Switch</string>
+ <string name="reset">Reset</string>
+ <string name="progress_bar">Progress Bar</string>
+ <string name="background_name">Set background color :</string>
+ <string name="default_themes">Apply Themes</string>
+ <string name="theme_name">Theme Name:</string>
+ <string name="multiple_intent">Chooser Activity</string>
+ <string name="default_background_color">#90CAF9</string>
+</resources>
diff --git a/tests/ThemePlayground/res/values/themes.xml b/tests/ThemePlayground/res/values/themes.xml
new file mode 100644
index 0000000..1211c92
--- /dev/null
+++ b/tests/ThemePlayground/res/values/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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
+ -->
+
+<resources>
+
+ <!--Theme for the app, it's defined empty here so it can be overlaid easily -->
+ <style name="Theme.App" parent="android:Theme.DeviceDefault">
+ </style>
+
+ <!--Theme for the dialog testing-->
+ <style name="Theme.Testing.Dialog.Alert" parent="android:Theme.DeviceDefault.Dialog.Alert">
+ </style>
+
+ <!--This Theme contains attributes required for components from the car support lib -->
+ <style name="PagedListTheme" parent="Theme.CarSupportWrapper.NoActionBar">
+ </style>
+</resources>
\ No newline at end of file
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
new file mode 100644
index 0000000..85989ae
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
@@ -0,0 +1,157 @@
+/*
+ * 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.themeplayground;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Handles the menu for the theme playground app
+ */
+public abstract class AbstractSampleActivity extends Activity implements
+ PopupMenu.OnMenuItemClickListener {
+
+ private UiModeManager mUiModeManager;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ mUiModeManager = (UiModeManager) this.getSystemService(Context.UI_MODE_SERVICE);
+ return true;
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.text_elements:
+ return startSampleActivity(TextSamples.class);
+ case R.id.panel_elements:
+ return startSampleActivity(ColorSamples.class);
+ case R.id.dialog_elements:
+ return startSampleActivity(DialogSamples.class);
+ case R.id.toggle_theme:
+ return toggleDayNight();
+ case R.id.widgets:
+ return startSampleActivity(WidgetsSamples.class);
+ case R.id.page_list_view:
+ return startSampleActivity(PagedListViewSamples.class);
+ case R.id.default_themes:
+ return startSampleActivity(DefaultThemeSamples.class);
+ case R.id.multiple_intent:
+ return startSampleActivity(MultipleIntentSamples.class);
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Will show the menu onclick of the menu button. This button will only appear when the theme is
+ * set to NoActionBar.
+ */
+ private void showPopupMenu(View v) {
+ PopupMenu popup = new PopupMenu(this, v);
+ popup.setOnMenuItemClickListener(this);
+ popup.inflate(R.menu.menu_main);
+ popup.show();
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ bindMenuButton();
+ }
+
+
+ /**
+ * When theme is set to NoActionBar then the menu also disappears blocking the user to navigate
+ * between the activities. At that point this method will bring up the menu button that will
+ * help user to navigate between activities.
+ */
+ private void bindMenuButton() {
+ Button buttonMenu = findViewById(R.id.button_menu);
+ if (Utils.sThemeName.equals("Theme.DeviceDefault.NoActionBar")) {
+ buttonMenu.setVisibility(View.VISIBLE);
+ } else {
+ buttonMenu.setVisibility(View.GONE);
+ }
+ buttonMenu.setOnClickListener(v -> {
+ showPopupMenu(v);
+ });
+ }
+
+ /**
+ * Launch the given sample activity
+ */
+ private boolean startSampleActivity(Class<?> cls) {
+ Intent dialogIntent = new Intent(this, cls);
+ startActivity(dialogIntent);
+ return true;
+ }
+
+ private boolean toggleDayNight() {
+ mUiModeManager.setNightMode(
+ (mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES)
+ ? UiModeManager.MODE_NIGHT_NO : UiModeManager.MODE_NIGHT_YES);
+ return true;
+ }
+
+ void setupBackgroundColorControls(int backgroundLayoutId) {
+ Button colorSetButton = findViewById(R.id.set_background_color);
+ ((EditText) findViewById(R.id.background_input_color)).setText(
+ R.string.default_background_color,
+ TextView.BufferType.EDITABLE);
+ colorSetButton.setOnClickListener(v -> {
+ String value = ((EditText) findViewById(R.id.background_input_color)).getText()
+ .toString();
+ try {
+ int color = Color.parseColor(value);
+ View dialogLayout = findViewById(backgroundLayoutId);
+ dialogLayout.setBackgroundColor(color);
+ } catch (Exception e) {
+ Toast.makeText(this, "not a color", Toast.LENGTH_LONG).show();
+ }
+ });
+ Button colorResetButton = findViewById(R.id.reset);
+ colorResetButton.setOnClickListener(v -> {
+ try {
+ View dialogLayout = findViewById(backgroundLayoutId);
+ dialogLayout.setBackgroundColor(android.R.color.black);
+ } catch (Exception e) {
+ Toast.makeText(this, "Something went Wrong. Try again later.",
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/ColorSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/ColorSamples.java
new file mode 100644
index 0000000..de82489
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/ColorSamples.java
@@ -0,0 +1,32 @@
+/*
+ * 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.themeplayground;
+
+import android.os.Bundle;
+
+/**
+ * Activity that renders a bunch of color values from the theme.
+ */
+public class ColorSamples extends AbstractSampleActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.panel_samples);
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/DefaultThemeSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/DefaultThemeSamples.java
new file mode 100644
index 0000000..1b87e82
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/DefaultThemeSamples.java
@@ -0,0 +1,80 @@
+/*
+ * 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.themeplayground;
+
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity that shows different device default themes. Auto complete themes come from
+ * theme_names.txt file in the raw folder. User can also input a valid theme in the textbox even if
+ * the theme is not available in the auto-complete.
+ */
+public class DefaultThemeSamples extends AbstractSampleActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.device_default_theme_samples);
+ Button buttonApply = findViewById(R.id.button_apply);
+ AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.theme_name);
+ ArrayAdapter<String> adapter =
+ new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+ listAllThemes());
+ textView.setAdapter(adapter);
+ buttonApply.setOnClickListener(v -> {
+ EditText input = findViewById(R.id.theme_name);
+ String themeName = input.getText().toString();
+ int themeResId = this.getResources().getIdentifier(themeName,
+ "style", "android");
+ if (themeResId == 0) {
+ Toast.makeText(this, "No such theme found. ",
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Toast.makeText(this, "Applying theme: " + themeName,
+ Toast.LENGTH_SHORT).show();
+ Utils.changeToTheme(this, themeName, themeResId);
+ });
+ }
+
+ private String[] listAllThemes() {
+ String data;
+ List<String> list = new ArrayList<>();
+ InputStream is = this.getResources().openRawResource(R.raw.theme_names);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ try {
+ while ((data = reader.readLine()) != null) {
+ list.add(data);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return list.toArray(new String[0]);
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java
new file mode 100644
index 0000000..87480f5
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java
@@ -0,0 +1,71 @@
+/*
+ * 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.themeplayground;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.ContextThemeWrapper;
+import android.widget.Button;
+import android.widget.TextView;
+
+
+/**
+ * Activity that shows different dialogs from the device default theme.
+ */
+public class DialogSamples extends AbstractSampleActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.dialog_samples);
+
+ Button mShowDialogBT = findViewById(R.id.showDialogBT);
+ Button mShowDialogWithCheckboxBT = findViewById(R.id.showDialogWithCheckboxBT);
+ setupBackgroundColorControls(R.id.dialogLayout);
+ mShowDialogBT.setOnClickListener(v -> openDialog(false));
+ mShowDialogWithCheckboxBT.setOnClickListener(v -> openDialog(true));
+ }
+
+
+ private void openDialog(boolean showCheckbox) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ new ContextThemeWrapper(this, R.style.Theme_Testing_Dialog_Alert));
+
+ if (showCheckbox) {
+ // Set Custom Title
+ TextView title = new TextView(this);
+ // Title Properties
+ title.setText("Custom Dialog Box");
+ builder.setCustomTitle(title);
+ builder.setMultiChoiceItems(new CharSequence[]{"I am a checkbox"},
+ new boolean[]{false},
+ (dialog, which, isChecked) -> {
+ });
+ } else {
+ builder.setTitle("Standard Alert Dialog")
+ .setMessage("With a message to show.");
+ }
+
+ builder.setPositiveButton("OK", (dialoginterface, i) -> {
+ }).setNegativeButton("CANCEL",
+ (dialog, which) -> {
+ });
+ builder.show();
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/MultipleIntentSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/MultipleIntentSamples.java
new file mode 100644
index 0000000..b6a9aa3
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/MultipleIntentSamples.java
@@ -0,0 +1,51 @@
+/*
+ * 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.themeplayground;
+
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Toast;
+
+/**
+ * Activity that shows how the Intent is handled when there are more than one application that could
+ * accept and handle the intent.
+ */
+public class MultipleIntentSamples extends AbstractSampleActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.multiple_intent_samples);
+ findViewById(R.id.fire_web_intent_button).setOnClickListener(
+ v -> triggerIntentResolverForWeb());
+ }
+
+ private void triggerIntentResolverForWeb() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("http://www.google.com"));
+
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivity(intent);
+ } else {
+ Toast.makeText(this, "No Application Installed to handle this action",
+ Toast.LENGTH_LONG).show();
+ }
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewAdapter.java b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewAdapter.java
new file mode 100644
index 0000000..2b23f15
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * 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.themeplayground;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link PagedListViewAdapter} that can be used with RecyclerViews.
+ */
+public class PagedListViewAdapter extends
+ RecyclerView.Adapter<PagedListViewAdapter.PagedListViewHolder> {
+
+ private ArrayList<String> mData;
+
+ PagedListViewAdapter(ArrayList<String> data) {
+ this.mData = data;
+ }
+
+ @NonNull
+ @Override
+ public PagedListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflator = LayoutInflater.from(parent.getContext());
+ View view = inflator.inflate(R.layout.item_list, parent, false);
+ return new PagedListViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PagedListViewHolder holder, int position) {
+ String title = mData.get(position);
+ holder.mTextTitle.setText(title);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mData.size();
+ }
+
+
+ /**
+ * Holds views for each element in the list.
+ */
+ public static class PagedListViewHolder extends RecyclerView.ViewHolder {
+ TextView mTextTitle;
+
+ PagedListViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mTextTitle = itemView.findViewById(R.id.textTitle);
+ }
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewSamples.java
new file mode 100644
index 0000000..c301abf
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedListViewSamples.java
@@ -0,0 +1,51 @@
+/*
+ * 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.themeplayground;
+
+import android.os.Bundle;
+
+import androidx.car.widget.PagedListView;
+
+import java.util.ArrayList;
+
+/**
+ * Activity that shows pagedlistView example with dummy data.
+ */
+public class PagedListViewSamples extends AbstractSampleActivity {
+
+ private final ArrayList<String> mData = new ArrayList<>();
+ private final int mDataToGenerate = 15;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.paged_list_view_samples);
+ PagedListView pagedListView = (PagedListView) findViewById(R.id.list);
+
+ PagedListViewAdapter pagedListAdapter = new PagedListViewAdapter(generateDummyData());
+ pagedListView.setAdapter(pagedListAdapter);
+ }
+
+ private ArrayList<String> generateDummyData() {
+ for (int i = 0; i <= mDataToGenerate; i++) {
+ mData.add("data" + i);
+ }
+ return mData;
+ }
+
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/TextSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/TextSamples.java
new file mode 100644
index 0000000..9b02050
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/TextSamples.java
@@ -0,0 +1,33 @@
+/*
+ * 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.themeplayground;
+
+import android.os.Bundle;
+
+/**
+ * Activity that shows text values from the device default theme
+ */
+public class TextSamples extends AbstractSampleActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.text_samples);
+ setupBackgroundColorControls(R.id.text_sample_list);
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/Utils.java b/tests/ThemePlayground/src/com/android/car/themeplayground/Utils.java
new file mode 100644
index 0000000..0990273
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/Utils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.themeplayground;
+
+import android.app.Activity;
+import android.content.Intent;
+
+/**
+ * Utility class for changing the theme of the app at run time.
+ */
+public class Utils {
+ static String sThemeName = "";
+ private static int sThemeResId = 0;
+
+ /**
+ * Set the theme of the Activity, and restart it by creating a new Activity of the same type.
+ */
+ public static void changeToTheme(Activity activity, String themeName, int themeResId) {
+ sThemeName = themeName;
+ sThemeResId = themeResId;
+ activity.finish();
+ activity.startActivity(new Intent(activity, activity.getClass()));
+ }
+
+ /** Set the theme of the activity, according to the configuration. */
+ public static void onActivityCreateSetTheme(Activity activity) {
+ if (sThemeName.equals("")) {
+ activity.setTheme(android.R.style.Theme_DeviceDefault);
+ } else {
+ activity.setTheme(sThemeResId);
+ }
+ }
+}
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/WidgetsSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/WidgetsSamples.java
new file mode 100644
index 0000000..c124f85
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/WidgetsSamples.java
@@ -0,0 +1,47 @@
+/*
+ * 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.themeplayground;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.Button;
+
+/**
+ * Activity that shows different widgets on configuration changes.
+ */
+public class WidgetsSamples extends AbstractSampleActivity {
+
+ private UiModeManager mUiModeManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Utils.onActivityCreateSetTheme(this);
+ setContentView(R.layout.widget_samples);
+ Button triggerConfigChanges = findViewById(R.id.trigger_config_change);
+ mUiModeManager = (UiModeManager) this.getSystemService(Context.UI_MODE_SERVICE);
+ setupBackgroundColorControls(R.id.widgetLayout);
+ triggerConfigChanges.setOnClickListener(v -> {
+ if (mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES) {
+ mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ } else {
+ mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
+ }
+ });
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
new file mode 100644
index 0000000..32f8e1d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.hal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.LongSupplier;
+
+@RunWith(AndroidJUnit4.class)
+public class InputHalServiceTest {
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock VehicleHal mVehicleHal;
+ @Mock InputHalService.InputListener mInputListener;
+ @Mock LongSupplier mUptimeSupplier;
+
+ private static final VehiclePropConfig HW_KEY_INPUT_CONFIG =
+ VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build();
+ private static final int DISPLAY = 42;
+
+ private enum Key { DOWN, UP }
+
+ private InputHalService mInputHalService;
+
+ @Before
+ public void setUp() {
+ when(mUptimeSupplier.getAsLong()).thenReturn(0L);
+ mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier);
+ mInputHalService.init();
+ }
+
+ @After
+ public void tearDown() {
+ mInputHalService.release();
+ mInputHalService = null;
+ }
+
+ @Test
+ public void ignoresSetListener_beforeKeyInputSupported() {
+ assertThat(mInputHalService.isKeyInputSupported()).isFalse();
+
+ mInputHalService.setInputListener(mInputListener);
+
+ mInputHalService.handleHalEvents(
+ ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER)));
+ verify(mInputListener, never()).onKeyEvent(any(), anyInt());
+ }
+
+ @Test
+ public void takesKeyInputProperty() {
+ Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
+ VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
+ HW_KEY_INPUT_CONFIG,
+ VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
+
+ Collection<VehiclePropConfig> takenProps =
+ mInputHalService.takeSupportedProperties(offeredProps);
+
+ assertThat(takenProps).containsExactly(HW_KEY_INPUT_CONFIG);
+ assertThat(mInputHalService.isKeyInputSupported()).isTrue();
+ }
+
+ @Test
+ public void dispatchesInputEvent_single_toListener() {
+ subscribeListener();
+
+ KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ }
+
+ @Test
+ public void dispatchesInputEvent_multiple_toListener() {
+ subscribeListener();
+
+ // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
+ // We need to make a copy of the information we need at the time of the call.
+ List<KeyEvent> events = new ArrayList<>();
+ doAnswer(inv -> {
+ KeyEvent event = inv.getArgument(0);
+ events.add(event.copy());
+ return null;
+ }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
+
+ mInputHalService.handleHalEvents(
+ ImmutableList.of(
+ makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER),
+ makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU)));
+
+ assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
+
+ events.forEach(KeyEvent::recycle);
+ }
+
+ @Test
+ public void handlesRepeatedKeys() {
+ subscribeListener();
+
+ KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getEventTime()).isEqualTo(0L);
+ assertThat(event.getDownTime()).isEqualTo(0L);
+ assertThat(event.getRepeatCount()).isEqualTo(0);
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(5L);
+ event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
+
+ assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getEventTime()).isEqualTo(5L);
+ assertThat(event.getDownTime()).isEqualTo(5L);
+ assertThat(event.getRepeatCount()).isEqualTo(1);
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(10L);
+ event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
+
+ assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getEventTime()).isEqualTo(10L);
+ assertThat(event.getDownTime()).isEqualTo(5L);
+ assertThat(event.getRepeatCount()).isEqualTo(0);
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(15L);
+ event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
+
+ assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
+ assertThat(event.getEventTime()).isEqualTo(15L);
+ assertThat(event.getDownTime()).isEqualTo(15L);
+ assertThat(event.getRepeatCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void handlesKeyUp_withoutKeyDown() {
+ subscribeListener();
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(42L);
+ KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
+
+ assertThat(event.getEventTime()).isEqualTo(42L);
+ assertThat(event.getDownTime()).isEqualTo(42L);
+ assertThat(event.getRepeatCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void separateKeyDownEvents_areIndependent() {
+ subscribeListener();
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(27L);
+ dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
+
+ when(mUptimeSupplier.getAsLong()).thenReturn(42L);
+ KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU);
+
+ assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
+ assertThat(event.getDownTime()).isEqualTo(42L);
+ assertThat(event.getRepeatCount()).isEqualTo(0);
+ }
+
+ private void subscribeListener() {
+ mInputHalService.takeSupportedProperties(ImmutableSet.of(HW_KEY_INPUT_CONFIG));
+ assertThat(mInputHalService.isKeyInputSupported()).isTrue();
+
+ mInputHalService.setInputListener(mInputListener);
+ verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT);
+ }
+
+
+ private VehiclePropValue makeKeyPropValue(Key action, int code) {
+ VehiclePropValue v = new VehiclePropValue();
+ v.prop = VehicleProperty.HW_KEY_INPUT;
+ v.value.int32Values.add(
+ (action == Key.DOWN
+ ? VehicleHwKeyInputAction.ACTION_DOWN
+ : VehicleHwKeyInputAction.ACTION_UP));
+ v.value.int32Values.add(code);
+ v.value.int32Values.add(DISPLAY);
+ return v;
+ }
+
+ private KeyEvent dispatchSingleEvent(Key action, int code) {
+ ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
+ reset(mInputListener);
+ mInputHalService.handleHalEvents(ImmutableList.of(makeKeyPropValue(action, code)));
+ verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
+ reset(mInputListener);
+ return captor.getValue();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 595f6e3..1947c8f 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -20,8 +20,10 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.car.settings.CarSettings;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +32,9 @@
import android.location.LocationManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
+import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -77,10 +81,15 @@
MockitoAnnotations.initMocks(this);
doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
doReturn(mLocationManager).when(mMockContext).getSystemService(Context.LOCATION_SERVICE);
+ doReturn(InstrumentationRegistry.getTargetContext().getContentResolver())
+ .when(mMockContext).getContentResolver();
mCarUserService = new CarUserService(mMockContext, mCarUserManagerHelper);
doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
+
+ // Restore default value at the beginning of each test.
+ putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 0);
}
/**
@@ -118,8 +127,6 @@
systemUser.id = UserHandle.USER_SYSTEM;
doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
- mockAdmin(10);
-
mCarUserService.onReceive(mMockContext,
new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -128,12 +135,29 @@
}
/**
+ * Test that the {@link CarUserService} does not set restrictions on user 0 if they have already
+ * been set.
+ */
+ @Test
+ public void testDoesNotSetSystemUserRestrictions_IfRestrictionsAlreadySet() {
+ // Mock system user.
+ UserInfo systemUser = new UserInfo();
+ systemUser.id = UserHandle.USER_SYSTEM;
+ doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+
+ putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mCarUserManagerHelper, never())
+ .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ }
+
+ /**
* Test that the {@link CarUserService} disable location service for user 0 upon first run.
*/
@Test
public void testDisableLocationForSystemUserOnFirstRun() {
- mockAdmin(/* adminId= */ 10);
-
mCarUserService.onReceive(mMockContext,
new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -162,13 +186,31 @@
* Test that the {@link CarUserService} sets default guest restrictions on first boot.
*/
@Test
- public void testInitializeGuestRestrictionsOnFirstRun() {
- mockAdmin(/* adminId= */ 10);
-
- mCarUserService.onReceive(mMockContext,
- new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
-
+ public void testInitializeGuestRestrictions_IfNotAlreadySet() {
+ mCarUserService.onReceive(mMockContext, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
verify(mCarUserManagerHelper).initDefaultGuestRestrictions();
+ assertThat(getSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET)).isEqualTo(1);
+ }
+
+ /**
+ * Test that the {@link CarUserService} does not set restrictions after they have been set once.
+ */
+ @Test
+ public void test_DoesNotInitializeGuestRestrictions_IfAlreadySet() {
+ putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
+ mCarUserService.onReceive(mMockContext, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+ verify(mCarUserManagerHelper, never()).initDefaultGuestRestrictions();
+ }
+
+ private void putSettingsInt(String key, int value) {
+ Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ key, value);
+ }
+
+ private int getSettingsInt(String key) {
+ return Settings.Global.getInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ key, /* default= */ 0);
}
private UserInfo mockAdmin(int adminId) {