aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-11-23 13:14:25 -0800
committerXin Li <delphij@google.com>2020-11-23 13:14:25 -0800
commit1690020f707b9763e8be93236e79acbf0492f970 (patch)
tree0ce45912b84db71690c634c61229d72d6d18c460
parent8a1d8d0b3281d0123265db548586e9d916d6c7b6 (diff)
parentfe2d325bfaa3d2819cef65bffa97f2a03300bf17 (diff)
downloadtests-temp_rvc_qpr_merge.tar.gz
Merge rvc-qpr-dev-plus-aosp-without-vendor@6881855temp_rvc_qpr_merge
Bug: 172690556 Merged-In: I41ee24f2b16b22fce688ba8e238ac4cff8335f7a Change-Id: I76dcd53e645f15496c562e1b3218715ad85c4a25
-rw-r--r--RotaryIME/Android.bp35
-rw-r--r--RotaryIME/AndroidManifest.xml31
-rw-r--r--RotaryIME/readme.md25
-rw-r--r--RotaryIME/res/color/key_background.xml21
-rw-r--r--RotaryIME/res/layout/horizontal_keyboard.xml196
-rw-r--r--RotaryIME/res/values/colors.xml22
-rw-r--r--RotaryIME/res/values/strings.xml67
-rw-r--r--RotaryIME/res/values/styles.xml28
-rw-r--r--RotaryIME/res/xml/ime.xml22
-rw-r--r--RotaryIME/src/com/android/car/rotaryime/RotaryIme.java181
-rw-r--r--RotaryPlayground/Android.bp6
-rw-r--r--RotaryPlayground/build.gradle58
-rw-r--r--RotaryPlayground/readme.md24
-rw-r--r--RotaryPlayground/res/drawable/selected_tab.xml25
-rw-r--r--RotaryPlayground/res/drawable/tab.xml20
-rw-r--r--RotaryPlayground/res/layout/custom_focus_areas_fragment.xml214
-rw-r--r--RotaryPlayground/res/layout/rotary_direct_manipulation.xml3
-rw-r--r--RotaryPlayground/res/layout/rotary_menu.xml32
-rw-r--r--RotaryPlayground/res/layout/rotary_sys_ui_direct_manipulation.xml6
-rw-r--r--RotaryPlayground/res/layout/rotary_web_view.xml67
-rw-r--r--RotaryPlayground/res/raw/web_view_html.xml130
-rw-r--r--RotaryPlayground/res/values/colors.xml1
-rw-r--r--RotaryPlayground/res/values/styles.xml (renamed from RotaryPlayground/res/drawable/seek_bar_background.xml)9
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/CustomFocusAreasFragment.java35
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java134
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/WebViewFragment.java69
-rw-r--r--TestMediaApp/build.gradle6
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java49
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java22
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java8
-rw-r--r--build.gradle2
-rw-r--r--gradle.properties1
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rw-r--r--readme.md61
-rw-r--r--settings.gradle6
-rwxr-xr-xtools/git_clone_projects.sh67
-rwxr-xr-xtools/go_rotary.sh74
37 files changed, 1644 insertions, 117 deletions
diff --git a/RotaryIME/Android.bp b/RotaryIME/Android.bp
new file mode 100644
index 0000000..bef2697
--- /dev/null
+++ b/RotaryIME/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_app {
+ name: "RotaryIME",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ sdk_version: "system_current",
+
+ static_libs: [
+ "car-ui-lib",
+ ],
+
+ owner: "google",
+ certificate: "shared",
+
+ optimize: {
+ enabled: false,
+ }
+}
diff --git a/RotaryIME/AndroidManifest.xml b/RotaryIME/AndroidManifest.xml
new file mode 100644
index 0000000..420175a
--- /dev/null
+++ b/RotaryIME/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.rotaryime">
+
+ <application android:label="@string/ime_name">
+ <service
+ android:name=".RotaryIme"
+ android:label="@string/ime_name"
+ android:permission="android.permission.BIND_INPUT_METHOD">
+ <intent-filter>
+ <action android:name="android.view.InputMethod" />
+ </intent-filter>
+ <meta-data android:name="android.view.im" android:resource="@xml/ime" />
+ </service>
+ </application>
+</manifest>
diff --git a/RotaryIME/readme.md b/RotaryIME/readme.md
new file mode 100644
index 0000000..50fcd16
--- /dev/null
+++ b/RotaryIME/readme.md
@@ -0,0 +1,25 @@
+# Rotary IME: Sample input method for rotary controller
+
+This is a sample IME for use with a rotary controller. It is intentionally very basic so that it's
+easy to understand the code. It doesn't support multiple locales / layouts. It doesn't support
+password fields, numeric fields, etc.
+
+## Building
+```
+make RotaryIME
+```
+
+## Installing
+```
+adb install out/target/product/[hardware]/system/app/RotaryIME/RotaryIME.apk
+```
+
+## Using
+
+Once installed, configure the `rotary_input_method` string resource in the
+`CarRotaryController` package to refer to this IME:
+```
+ <string name="rotary_input_method" translatable="false">com.android.car.rotaryime/.RotaryIme</string>
+```
+Then build and install `CarRotaryController`. There is no need to enable this
+IME or select it; the `RotaryService` will select it automatically in rotary mode.
diff --git a/RotaryIME/res/color/key_background.xml b/RotaryIME/res/color/key_background.xml
new file mode 100644
index 0000000..1a4a2c4
--- /dev/null
+++ b/RotaryIME/res/color/key_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@color/key_background_pressed" />
+ <item android:state_focused="true" android:color="@color/key_background_focused" />
+ <item android:color="@color/key_background_normal" />
+</selector>
diff --git a/RotaryIME/res/layout/horizontal_keyboard.xml b/RotaryIME/res/layout/horizontal_keyboard.xml
new file mode 100644
index 0000000..7c1bff6
--- /dev/null
+++ b/RotaryIME/res/layout/horizontal_keyboard.xml
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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="vertical"
+ android:background="@color/keyboard_background">
+ <com.android.car.ui.FocusParkingView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <com.android.car.ui.FocusArea
+ android:id="@+id/letters"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/a"
+ android:text="@string/key_label_a"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_b"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_c"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_d"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_e"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_f"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_g"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_h"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_i"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_j"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_k"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_l"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_m"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_n"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_o"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_p"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_q"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_r"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_s"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_t"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_u"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_v"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_w"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_x"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_y"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_z"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/delete"
+ android:text="@string/key_label_delete"
+ style="@style/Key" />
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/other_keys"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/close"
+ android:text="@string/key_close_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/dash"
+ android:text="@string/key_dash_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/quote"
+ android:text="@string/key_quote_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/apostrophe"
+ android:text="@string/key_apostropohe_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/exclamation_mark"
+ android:text="@string/key_exclamation_mark_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/question_mark"
+ android:text="@string/key_question_mark_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/semicolon"
+ android:text="@string/key_semicolon_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/colon"
+ android:text="@string/key_colon_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/comma"
+ android:text="@string/key_comma_label"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/period"
+ android:text="@string/key_period_label"
+ style="@style/Key" />
+ <TextView
+ android:layout_width="120dp"
+ android:id="@+id/space"
+ style="@style/Key" />
+ <TextView
+ android:id="@+id/zero"
+ android:text="@string/key_label_0"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_1"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_2"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_3"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_4"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_5"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_6"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_7"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_8"
+ style="@style/Key" />
+ <TextView
+ android:text="@string/key_label_9"
+ style="@style/Key" />
+ </com.android.car.ui.FocusArea>
+</LinearLayout>
diff --git a/RotaryIME/res/values/colors.xml b/RotaryIME/res/values/colors.xml
new file mode 100644
index 0000000..4e6e535
--- /dev/null
+++ b/RotaryIME/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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="keyboard_background">#2c2c2c</color>
+ <color name="key_background_normal">#404040</color>
+ <color name="key_background_focused">#6080c0</color>
+ <color name="key_background_pressed">#a0c0ff</color>
+</resources>
diff --git a/RotaryIME/res/values/strings.xml b/RotaryIME/res/values/strings.xml
new file mode 100644
index 0000000..8ff29a7
--- /dev/null
+++ b/RotaryIME/res/values/strings.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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="ime_name">Rotary</string>
+ <string name="label_subtype_generic">%s</string>
+ <string name="key_label_a">A</string>
+ <string name="key_label_b">B</string>
+ <string name="key_label_c">C</string>
+ <string name="key_label_d">D</string>
+ <string name="key_label_e">E</string>
+ <string name="key_label_f">F</string>
+ <string name="key_label_g">G</string>
+ <string name="key_label_h">H</string>
+ <string name="key_label_i">I</string>
+ <string name="key_label_j">J</string>
+ <string name="key_label_k">K</string>
+ <string name="key_label_l">L</string>
+ <string name="key_label_m">M</string>
+ <string name="key_label_n">N</string>
+ <string name="key_label_o">O</string>
+ <string name="key_label_p">P</string>
+ <string name="key_label_q">Q</string>
+ <string name="key_label_r">R</string>
+ <string name="key_label_s">S</string>
+ <string name="key_label_t">T</string>
+ <string name="key_label_u">U</string>
+ <string name="key_label_v">V</string>
+ <string name="key_label_w">W</string>
+ <string name="key_label_x">X</string>
+ <string name="key_label_y">Y</string>
+ <string name="key_label_z">Z</string>
+ <string name="key_label_delete">⌫</string>
+ <string name="key_close_label">▼</string>
+ <string name="key_dash_label">-</string>
+ <string name="key_quote_label">\"</string>
+ <string name="key_apostropohe_label">\'</string>
+ <string name="key_exclamation_mark_label">!</string>
+ <string name="key_question_mark_label">\?</string>
+ <string name="key_semicolon_label">;</string>
+ <string name="key_colon_label">:</string>
+ <string name="key_comma_label">,</string>
+ <string name="key_period_label">.</string>
+ <string name="key_label_0">0</string>
+ <string name="key_label_1">1</string>
+ <string name="key_label_2">2</string>
+ <string name="key_label_3">3</string>
+ <string name="key_label_4">4</string>
+ <string name="key_label_5">5</string>
+ <string name="key_label_6">6</string>
+ <string name="key_label_7">7</string>
+ <string name="key_label_8">8</string>
+ <string name="key_label_9">9</string>
+</resources>
diff --git a/RotaryIME/res/values/styles.xml b/RotaryIME/res/values/styles.xml
new file mode 100644
index 0000000..6604d6f
--- /dev/null
+++ b/RotaryIME/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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>
+ <style name="Key">
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:layout_margin">2dp</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@color/key_background</item>
+ <item name="android:textColor">#ffffff</item>
+ <item name="android:textSize">30dp</item>
+ </style>
+</resources>
diff --git a/RotaryIME/res/xml/ime.xml b/RotaryIME/res/xml/ime.xml
new file mode 100644
index 0000000..41e3eec
--- /dev/null
+++ b/RotaryIME/res/xml/ime.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+ <subtype
+ android:label="@string/label_subtype_generic"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard"/>
+</input-method>
diff --git a/RotaryIME/src/com/android/car/rotaryime/RotaryIme.java b/RotaryIME/src/com/android/car/rotaryime/RotaryIme.java
new file mode 100644
index 0000000..1d63e2f
--- /dev/null
+++ b/RotaryIME/src/com/android/car/rotaryime/RotaryIme.java
@@ -0,0 +1,181 @@
+package com.android.car.rotaryime;
+
+import static android.view.KeyEvent.KEYCODE_1;
+import static android.view.KeyEvent.KEYCODE_A;
+import static android.view.KeyEvent.KEYCODE_APOSTROPHE;
+import static android.view.KeyEvent.KEYCODE_COMMA;
+import static android.view.KeyEvent.KEYCODE_DEL;
+import static android.view.KeyEvent.KEYCODE_PERIOD;
+import static android.view.KeyEvent.KEYCODE_SEMICOLON;
+import static android.view.KeyEvent.KEYCODE_SLASH;
+import static android.view.KeyEvent.KEYCODE_SPACE;
+import static android.view.KeyEvent.META_SHIFT_ON;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+
+/**
+ * Sample IME for rotary controllers. This is intentionally very basic so that it's easy to
+ * understand the code. It doesn't support multiple locales / layouts. It doesn't support password
+ * fields, numeric fields, etc.
+ */
+public class RotaryIme extends InputMethodService {
+
+ /** Message requesting that the view in the {@code obj} field have its pressed state cleared. */
+ private static final int MSG_CLEAR_PRESSED = 1;
+
+ /** How many milliseconds a key should remain pressed after the user clicks it. */
+ private static final long PRESSED_MS = 200;
+
+ /** A handler for clearing the pressed state shortly after a key is pressed. */
+ private final Handler mHandler = new ImeHandler(Looper.getMainLooper());
+
+ @Override
+ public boolean onEvaluateFullscreenMode() {
+ // Don't go full-screen, even in landscape. This IME is very short so it fits easily.
+ return false;
+ }
+
+ @Override
+ public View onCreateInputView() {
+ ViewGroup rootView = (ViewGroup) getLayoutInflater().inflate(R.layout.horizontal_keyboard,
+ /* root= */ null);
+
+ // Since the IME isn't in the application window, when the user presses the center button on the
+ // rotary controller to press the focused key on this keyboard, ACTION_CLICK will be performed
+ // on the key which will invoke its click handler. Long press will similarly result in the key's
+ // long click handler being invoked.
+
+ // The first row contains letters in alphabetical order and the delete key.
+ ViewGroup letters = rootView.findViewById(R.id.letters);
+ int aIndex = findChild(letters, rootView.findViewById(R.id.a));
+ for (int i = 0; i < 26; i++) {
+ TextView keyTextView = (TextView) letters.getChildAt(aIndex + i);
+ int keyCode = KEYCODE_A + i;
+ keyTextView.setOnClickListener(view -> handleKeyClick(view, keyCode));
+ keyTextView.setOnLongClickListener(view -> handleKeyLongClick(view, keyCode));
+ }
+
+ rootView.findViewById(R.id.delete).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_DEL));
+
+ // The second row contains the close key, symbols, space, and digits.
+ rootView.findViewById(R.id.close).setOnClickListener(
+ view -> requestHideSelf(/* flags= */ 0));
+ rootView.findViewById(R.id.dash).setOnClickListener(
+ view -> handleKeyClick(view, KeyEvent.KEYCODE_MINUS));
+ rootView.findViewById(R.id.quote).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_APOSTROPHE, META_SHIFT_ON));
+ rootView.findViewById(R.id.apostrophe).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_APOSTROPHE));
+ rootView.findViewById(R.id.exclamation_mark).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_1, META_SHIFT_ON));
+ rootView.findViewById(R.id.question_mark).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_SLASH, META_SHIFT_ON));
+ rootView.findViewById(R.id.semicolon).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_SEMICOLON));
+ rootView.findViewById(R.id.colon).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_SEMICOLON, META_SHIFT_ON));
+ rootView.findViewById(R.id.comma).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_COMMA));
+ rootView.findViewById(R.id.period).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_PERIOD));
+ rootView.findViewById(R.id.space).setOnClickListener(
+ view -> handleKeyClick(view, KEYCODE_SPACE));
+ ViewGroup otherKeys = rootView.findViewById(R.id.other_keys);
+ int zeroIndex = findChild(otherKeys, rootView.findViewById(R.id.zero));
+ for (int i = 0; i < 10; i++) {
+ int keyCode = KeyEvent.KEYCODE_0 + i;
+ otherKeys.getChildAt(zeroIndex + i).setOnClickListener(
+ view -> handleKeyClick(view, keyCode));
+ }
+
+ return rootView;
+ }
+
+ /** Returns {@code child}'s index within {@code parent} or -1 if not found. */
+ private static int findChild(ViewGroup parent, View child) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ if (parent.getChildAt(i) == child) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Handles a click on the key {@code view} by sending events with the given {@code keyCode}. Use
+ * this convenience method for unshifted keys.
+ */
+ private void handleKeyClick(View view, int keyCode) {
+ handleKeyClick(view, keyCode, /* metaState= */ 0);
+ }
+
+ /**
+ * Handles a click on the key {@code view} by sending events with the given {@code keyCode}
+ * and {@code metaState}. Use {@link KeyEvent#META_SHIFT_ON} to access shifted keys such as
+ * question mark (shift slash). Use zero for unshifted keys.
+ */
+ private void handleKeyClick(View view, int keyCode, int metaState) {
+ animatePressed(view);
+ sendDownUpKeyEvents(keyCode, metaState);
+ }
+
+ /**
+ * Handles a long click on the key {@code view} by sending events with the given {@code keyCode}
+ * and {@link KeyEvent#META_SHIFT_ON} to produce shifted keys such as capital letters.
+ */
+ private boolean handleKeyLongClick(View view, int keyCode) {
+ animatePressed(view);
+ sendDownUpKeyEvents(keyCode, META_SHIFT_ON);
+ return true;
+ }
+
+ /**
+ * Sends an {@link KeyEvent#ACTION_DOWN} event followed by an {@link KeyEvent#ACTION_UP} event
+ * with the given {@code keyCode} and {@code metaState}.
+ */
+ private void sendDownUpKeyEvents(int keyCode, int metaState) {
+ long uptimeMillis = SystemClock.uptimeMillis();
+ KeyEvent downEvent = new KeyEvent(uptimeMillis, uptimeMillis, KeyEvent.ACTION_DOWN,
+ keyCode, /* repeat= */ 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD, InputDevice.SOURCE_KEYBOARD);
+ KeyEvent upEvent = new KeyEvent(uptimeMillis, uptimeMillis, KeyEvent.ACTION_UP,
+ keyCode, /* repeat= */ 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD, InputDevice.SOURCE_KEYBOARD);
+ getCurrentInputConnection().sendKeyEvent(downEvent);
+ getCurrentInputConnection().sendKeyEvent(upEvent);
+ }
+
+ /** Sets {@code view}'s pressed state and clears it {@link #PRESSED_MS} later. */
+ private void animatePressed(View view) {
+ view.setPressed(true);
+ Message message = mHandler.obtainMessage(MSG_CLEAR_PRESSED, view);
+ mHandler.removeMessages(MSG_CLEAR_PRESSED);
+ mHandler.sendMessageDelayed(message, PRESSED_MS);
+ }
+
+ /** A handler for clearing the pressed state shortly after a key is pressed. */
+ private static class ImeHandler extends Handler {
+ ImeHandler(@NonNull Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ if (msg.what == MSG_CLEAR_PRESSED) {
+ ((View) msg.obj).setPressed(false);
+ }
+ }
+ }
+}
diff --git a/RotaryPlayground/Android.bp b/RotaryPlayground/Android.bp
index b8304ad..03583ba 100644
--- a/RotaryPlayground/Android.bp
+++ b/RotaryPlayground/Android.bp
@@ -23,14 +23,16 @@ android_app {
sdk_version: "system_current",
static_libs: [
- "androidx-constraintlayout_constraintlayout",
- "car-apps-common",
"car-ui-lib",
],
owner: "google",
certificate: "shared",
+ dex_preopt: {
+ enabled: false,
+ },
+
optimize: {
enabled: false,
}
diff --git a/RotaryPlayground/build.gradle b/RotaryPlayground/build.gradle
new file mode 100644
index 0000000..fa8d79c
--- /dev/null
+++ b/RotaryPlayground/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 30
+ defaultConfig {
+ applicationId "com.android.car.rotaryplayground"
+ minSdkVersion 30
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ lintOptions {
+ abortOnError false
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+ }
+}
+
+dependencies {
+ implementation "androidx.preference:preference:1.1.1"
+ implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta8"
+ implementation project(":car-ui-lib")
+}
diff --git a/RotaryPlayground/readme.md b/RotaryPlayground/readme.md
index ee74185..715e2cc 100644
--- a/RotaryPlayground/readme.md
+++ b/RotaryPlayground/readme.md
@@ -13,4 +13,26 @@ adb install out/target/product/[hardware]/system/app/RotaryPlayground/RotaryPlay
## Once installed, launch Rotary Playground in the Launcher, or with this adb command:
```
adb shell am start -n com.android.car.rotaryplayground/com.android.car.rotaryplayground.RotaryActivity
-``` \ No newline at end of file
+```
+
+## Tools
+
+### goRotary.sh
+This script helps you to build, install and run the app.
+
+* To build
+```
+cd $ANDROID_BUILD_TOP
+packages/apps/Car/tests/tools/goRotary.sh b
+```
+The apks and android.car.jar are in /tmp/rotary by default.
+
+* To install
+```
+packages/apps/Car/tests/tools/goRotary.sh i
+```
+
+* To run
+```
+packages/apps/Car/tests/tools/goRotary.sh r
+```
diff --git a/RotaryPlayground/res/drawable/selected_tab.xml b/RotaryPlayground/res/drawable/selected_tab.xml
new file mode 100644
index 0000000..9b694f7
--- /dev/null
+++ b/RotaryPlayground/res/drawable/selected_tab.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:angle="0"
+ android:startColor="@color/tab_color"
+ android:centerColor="@android:color/transparent"
+ android:centerX="0.08" />
+</shape>
diff --git a/RotaryPlayground/res/drawable/tab.xml b/RotaryPlayground/res/drawable/tab.xml
new file mode 100644
index 0000000..b943ad5
--- /dev/null
+++ b/RotaryPlayground/res/drawable/tab.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:drawable="@drawable/selected_tab" />
+</selector>
diff --git a/RotaryPlayground/res/layout/custom_focus_areas_fragment.xml b/RotaryPlayground/res/layout/custom_focus_areas_fragment.xml
new file mode 100644
index 0000000..ea6a0b0
--- /dev/null
+++ b/RotaryPlayground/res/layout/custom_focus_areas_fragment.xml
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.android.car.ui.FocusArea
+ android:id="@+id/top_left"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_background_color">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:focusedByDefault="true"
+ android:text="android:focusedByDefault"/>
+ <Button
+ android:id="@+id/default_focus1"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ </com.android.car.ui.FocusArea>
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_background_color"
+ app:defaultFocus="@+id/default_focus">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:focusedByDefault="true"
+ android:text="android:focusedByDefault"/>
+ <Button
+ android:id="@+id/default_focus"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="app:defaultFocus"/>
+ </com.android.car.ui.FocusArea>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_background_color"
+ app:defaultFocus="@+id/default_focus2">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:id="@+id/default_focus2"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="app:defaultFocus"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ </com.android.car.ui.FocusArea>
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_background_color"
+ app:nudgeLeft="@+id/top_left">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Nudging to left goes to top left"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ </com.android.car.ui.FocusArea>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_background_color"
+ app:nudgeShortcut="@+id/nudge_shortcut"
+ app:nudgeShortcutDirection="up">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Nudging up goes to the first Button"/>
+ <Button
+ android:id="@+id/nudge_shortcut"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:text="Button"/>
+ </com.android.car.ui.FocusArea>
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="@dimen/margin"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/card_disabled_background_color">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:focusable="false"
+ android:text="non-focusable Button"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/button_height"
+ android:paddingHorizontal="@dimen/padding"
+ android:enabled="false"
+ android:text="disabled Button"/>
+ </com.android.car.ui.FocusArea>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/RotaryPlayground/res/layout/rotary_direct_manipulation.xml b/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
index 6f6990e..a38cbe0 100644
--- a/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
+++ b/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
@@ -63,8 +63,7 @@
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/seek_bar_background">
+ android:layout_height="wrap_content">
</SeekBar>
<RadialTimePickerView
android:id="@+id/radial_time_picker"
diff --git a/RotaryPlayground/res/layout/rotary_menu.xml b/RotaryPlayground/res/layout/rotary_menu.xml
index df3ec19..f72d6ec 100644
--- a/RotaryPlayground/res/layout/rotary_menu.xml
+++ b/RotaryPlayground/res/layout/rotary_menu.xml
@@ -25,35 +25,55 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Cards" />
+ android:text="Cards"
+ style="@style/tab" />
<Button
android:id="@+id/direct_manipulation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Direct Manipulation" />
+ android:text="Direct Manipulation"
+ style="@style/tab" />
<Button
android:id="@+id/sys_ui_direct_manipulation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Sys UI Manipulation" />
+ android:text="Sys UI Manipulation"
+ style="@style/tab" />
<Button
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Grid" />
+ android:text="Grid"
+ style="@style/tab" />
<Button
android:id="@+id/notification"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Notification" />
+ android:text="Notification"
+ style="@style/tab" />
<Button
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:text="Scroll" />
+ android:text="Scroll"
+ style="@style/tab" />
+ <Button
+ android:id="@+id/web_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:text="WebView"
+ style="@style/tab" />
+ <Button
+ android:id="@+id/custom_focus_areas"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:text="Custom FocusAreas"
+ style="@style/tab" />
</com.android.car.ui.FocusArea>
diff --git a/RotaryPlayground/res/layout/rotary_sys_ui_direct_manipulation.xml b/RotaryPlayground/res/layout/rotary_sys_ui_direct_manipulation.xml
index 9e30a1d..37782f9 100644
--- a/RotaryPlayground/res/layout/rotary_sys_ui_direct_manipulation.xml
+++ b/RotaryPlayground/res/layout/rotary_sys_ui_direct_manipulation.xml
@@ -46,8 +46,7 @@ RotaryController#TREAT_APP_WINDOW_AS_SYSTEM_WINDOW constant to true.
<SeekBar
android:id="@+id/direct_manipulation_supported_seek_bar"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/seek_bar_background">
+ android:layout_height="wrap_content">
</SeekBar>
<RadialTimePickerView
android:id="@+id/direct_manipulation_supported_radial_time_picker"
@@ -72,8 +71,7 @@ RotaryController#TREAT_APP_WINDOW_AS_SYSTEM_WINDOW constant to true.
<SeekBar
android:id="@+id/direct_manipulation_unsupported_seek_bar"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/seek_bar_background">
+ android:layout_height="wrap_content">
</SeekBar>
<RadialTimePickerView
android:id="@+id/direct_manipulation_unsupported_radial_time_picker"
diff --git a/RotaryPlayground/res/layout/rotary_web_view.xml b/RotaryPlayground/res/layout/rotary_web_view.xml
new file mode 100644
index 0000000..7995dc6
--- /dev/null
+++ b/RotaryPlayground/res/layout/rotary_web_view.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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="match_parent"
+ android:orientation="vertical">
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/toggle_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:text="Toggle Buttons"/>
+
+ </com.android.car.ui.FocusArea>
+
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/top_button"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:onClick="onRotaryButtonClick"
+ android:tag="test_button"
+ android:text="Top Button"
+ android:visibility="gone" />
+
+ <WebView
+ android:id="@+id/web_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/bottom_button"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:onClick="onRotaryButtonClick"
+ android:tag="test_button"
+ android:text="Bottom Button"
+ android:visibility="gone" />
+
+ </com.android.car.ui.FocusArea>
+
+</LinearLayout> \ No newline at end of file
diff --git a/RotaryPlayground/res/raw/web_view_html.xml b/RotaryPlayground/res/raw/web_view_html.xml
new file mode 100644
index 0000000..d6e128a
--- /dev/null
+++ b/RotaryPlayground/res/raw/web_view_html.xml
@@ -0,0 +1,130 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<html>
+ <head>
+ <title>Sample Web Page</title>
+ </head>
+
+ <body>
+
+ <h1>1. Lists</h1>
+ <ol>
+ <li><b>First</b></li>
+ <li><i>Second</i></li>
+ <li>Third</li>
+ <li>Fourth</li>
+ <li>Fifth</li>
+ </ol>
+ <ul>
+ <li><b>1st</b></li>
+ <li><i>2nd</i></li>
+ <li>3rd</li>
+ <li>4th</li>
+ <li>5th</li>
+ </ul>
+
+ <h1>2. Text</h1>
+ <p>Paragraph</p>
+ <p>Another paragraph</p>
+
+ <h1>3. Links</h1>
+ <a href="https://en.wikipedia.org/wiki/Breakout_(video_game)">Breakout</a>
+ <br/>
+ <a href="https://en.wikipedia.org/wiki/Pong">Pong</a>
+ <br/>
+ <a href="https://en.wikipedia.org/wiki/Space_Invaders">Space Invaders</a>
+ <br/>
+ <a href="https://en.wikipedia.org/wiki/Lunar_Lander_(1979_video_game)">Lunar Lander</a>
+ <a href="https://en.wikipedia.org/wiki/Asteroids_(video_game)">Asteroids</a>
+ <br/>
+ <a href="https://en.wikipedia.org/wiki/Battlezone_(1980_video_game)">Battlezone</a>
+ <a href="https://en.wikipedia.org/wiki/Centipede_(video_game)">Centipede</a>
+ <a href="https://en.wikipedia.org/wiki/Missile_Command">Missile Command</a>
+
+ <h1>4. Lists</h1>
+ <ol>
+ <li><b>Sixth</b></li>
+ <li><i>Seventh</i></li>
+ <li>Eighth</li>
+ <li>Ninth</li>
+ <li>Tenth</li>
+ </ol>
+ <ul>
+ <li><b>6th</b></li>
+ <li><i>7th</i></li>
+ <li>8th</li>
+ <li>9th</li>
+ <li>10th</li>
+ </ul>
+
+ <h1>5. More Lists</h1>
+ <ol>
+ <li><b>Eleventh</b></li>
+ <li><i>Twelfth</i></li>
+ <li>Thirteenth</li>
+ <li>Fourteenth</li>
+ <li>Fifteenth</li>
+ </ol>
+ <ul>
+ <li><b>11th</b></li>
+ <li><i>12th</i></li>
+ <li>13th</li>
+ <li>14th</li>
+ <li>15th</li>
+ </ul>
+
+ <h1>6. More Links</h1>
+ <a href="https://en.wikipedia.org/wiki/Defender_(1981_video_game)">Defender</a>
+ <a href="https://en.wikipedia.org/wiki/Donkey_Kong_(video_game)">Donkey Kong</a>
+ <a href="https://en.wikipedia.org/wiki/Tempest_(video_game)">Tempest</a>
+ <a href="https://en.wikipedia.org/wiki/Frogger">Frogger</a>
+ <a href="https://en.wikipedia.org/wiki/Qix">Qix</a>
+
+ <h1>7. Lists</h1>
+ <ol>
+ <li><b>Sixteenth</b></li>
+ <li><i>Seventeenth</i></li>
+ <li>Eighteenth</li>
+ <li>Nineteenth</li>
+ <li>Twentieth</li>
+ </ol>
+ <ul>
+ <li><b>16th</b></li>
+ <li><i>17th</i></li>
+ <li>18th</li>
+ <li>19th</li>
+ <li>20th</li>
+ </ul>
+
+ <h1>8. More Lists</h1>
+ <ol>
+ <li><b>Twenty-first</b></li>
+ <li><i>Twenty-second</i></li>
+ <li>Twenty-third</li>
+ <li>Twenty-fourth</li>
+ <li>Twenty-fifth</li>
+ </ol>
+ <ul>
+ <li><b>21st</b></li>
+ <li><i>22nd</i></li>
+ <li>23rd</li>
+ <li>24th</li>
+ <li>25th</li>
+ </ul>
+
+ </body>
+</html>
diff --git a/RotaryPlayground/res/values/colors.xml b/RotaryPlayground/res/values/colors.xml
index 87480a0..ca3e4c6 100644
--- a/RotaryPlayground/res/values/colors.xml
+++ b/RotaryPlayground/res/values/colors.xml
@@ -21,4 +21,5 @@
<color name="button_background_color">#660000</color>
<color name="button_disabled_background_color">#61646b</color>
<color name="scroll_text_background_color">#61646b</color>
+ <color name="tab_color">#808080</color>
</resources> \ No newline at end of file
diff --git a/RotaryPlayground/res/drawable/seek_bar_background.xml b/RotaryPlayground/res/values/styles.xml
index 8d66a3b..f195f5c 100644
--- a/RotaryPlayground/res/drawable/seek_bar_background.xml
+++ b/RotaryPlayground/res/values/styles.xml
@@ -14,7 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/car_ui_rotary_focus_color"
- android:radius="16dp" />
+<resources>
+ <style name="tab" parent="ButtonStyle">
+ <item name="android:background">@drawable/tab</item>
+ </style>
+</resources>
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/CustomFocusAreasFragment.java b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomFocusAreasFragment.java
new file mode 100644
index 0000000..b8b1c71
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomFocusAreasFragment.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.rotaryplayground;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+/** Fragment to demo custom attributes in {@link com.android.car.ui.FocusArea}. */
+public class CustomFocusAreasFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.custom_focus_areas_fragment, container, false);
+ }
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
index c87fe1b..0e417b4 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
@@ -37,65 +37,81 @@ public class RotaryMenu extends Fragment {
private Fragment mSysUiDirectManipulation = null;
private Fragment mNotificationFragment = null;
private Fragment mScrollFragment = null;
-
- private Button mCardButton;
- private Button mGridButton;
- private Button mDirectManipulationButton;
- private Button mSysUiDirectManipulationButton;
- private Button mNotificationButton;
- private Button mScrollButton;
+ private Fragment mWebViewFragment = null;
+ private Fragment mCustomFocusAreasFragment = null;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.rotary_menu, container, false);
- mCardButton = view.findViewById(R.id.cards);
- mCardButton.setOnFocusChangeListener((v, hasFocus) -> showRotaryCards(hasFocus));
- mCardButton.setOnClickListener(v -> showRotaryCards(/* hasFocus= */ true));
-
- mGridButton = view.findViewById(R.id.grid);
- mGridButton.setOnFocusChangeListener((v, hasFocus) -> showGridExample(hasFocus));
- mGridButton.setOnClickListener(v -> showGridExample(/* hasFocus= */ true));
-
- mDirectManipulationButton = view.findViewById(R.id.direct_manipulation);
- mDirectManipulationButton.setOnFocusChangeListener(
- (v, hasFocus) -> showDirectManipulationExamples(hasFocus));
- mDirectManipulationButton.setOnClickListener(
- (v -> showDirectManipulationExamples(/* hasFocus= */ true)));
-
- mSysUiDirectManipulationButton = view.findViewById(R.id.sys_ui_direct_manipulation);
- mSysUiDirectManipulationButton.setOnFocusChangeListener(
- (v, hasFocus) -> showSysUiDirectManipulationExamples(hasFocus));
- mSysUiDirectManipulationButton.setOnClickListener(
- (v -> showSysUiDirectManipulationExamples(/* hasFocus= */ true)));
-
- mNotificationButton = view.findViewById(R.id.notification);
- mNotificationButton.setOnFocusChangeListener(
- (v, hasFocus) -> showNotificationExample(hasFocus));
- mNotificationButton.setOnClickListener(v -> showNotificationExample(/* hasFocus= */ true));
-
- mScrollButton = view.findViewById(R.id.scroll);
- mScrollButton.setOnFocusChangeListener((v, hasFocus) -> showScrollFragment(hasFocus));
- mScrollButton.setOnClickListener(v -> showScrollFragment(/* hasFocus= */ true));
+ Button cardButton = view.findViewById(R.id.cards);
+ cardButton.setOnClickListener(v -> {
+ selectTab(v);
+ showRotaryCards();
+ });
+
+ Button gridButton = view.findViewById(R.id.grid);
+ gridButton.setOnClickListener(v -> {
+ selectTab(v);
+ showGridExample();
+ });
+
+ Button directManipulationButton = view.findViewById(R.id.direct_manipulation);
+ directManipulationButton.setOnClickListener(v -> {
+ selectTab(v);
+ showDirectManipulationExamples();
+ });
+
+ Button sysUiDirectManipulationButton = view.findViewById(R.id.sys_ui_direct_manipulation);
+ sysUiDirectManipulationButton.setOnClickListener(v -> {
+ selectTab(v);
+ showSysUiDirectManipulationExamples();
+ });
+
+ Button notificationButton = view.findViewById(R.id.notification);
+ notificationButton.setOnClickListener(v -> {
+ selectTab(v);
+ showNotificationExample();
+ });
+
+ Button scrollButton = view.findViewById(R.id.scroll);
+ scrollButton.setOnClickListener(v -> {
+ selectTab(v);
+ showScrollFragment();
+ });
+
+ Button webViewButton = view.findViewById(R.id.web_view);
+ webViewButton.setOnClickListener(v -> {
+ selectTab(v);
+ showWebViewFragment();
+ });
+
+ Button customFocusAreasButton = view.findViewById(R.id.custom_focus_areas);
+ customFocusAreasButton.setOnClickListener(v -> {
+ selectTab(v);
+ showCustomFocusAreasFragment();
+ });
return view;
}
- private void showRotaryCards(boolean hasFocus) {
- if (!hasFocus) {
- return; // Do nothing if no focus.
+ private void selectTab(View view) {
+ ViewGroup container = (ViewGroup) view.getParent();
+ for (int i = 0; i < container.getChildCount(); i++) {
+ container.getChildAt(i).setSelected(false);
}
+ view.setSelected(true);
+ }
+
+ private void showRotaryCards() {
if (mRotaryCards == null) {
mRotaryCards = new RotaryCards();
}
showFragment(mRotaryCards);
}
- private void showGridExample(boolean hasFocus) {
- if (!hasFocus) {
- return; // do nothing if no focus.
- }
+ private void showGridExample() {
if (mRotaryGrid == null) {
mRotaryGrid = new RotaryGrid();
}
@@ -104,46 +120,48 @@ public class RotaryMenu extends Fragment {
// TODO(agathaman): refactor this and the showRotaryCards above into a
// showFragment(Fragment fragment, boolean hasFocus); method.
- private void showDirectManipulationExamples(boolean hasFocus) {
- if (!hasFocus) {
- return; // Do nothing if no focus.
- }
+ private void showDirectManipulationExamples() {
if (mDirectManipulation == null) {
mDirectManipulation = new RotaryDirectManipulationWidgets();
}
showFragment(mDirectManipulation);
}
- private void showSysUiDirectManipulationExamples(boolean hasFocus) {
- if (!hasFocus) {
- return; // Do nothing if no focus.
- }
+ private void showSysUiDirectManipulationExamples() {
if (mSysUiDirectManipulation == null) {
mSysUiDirectManipulation = new RotarySysUiDirectManipulationWidgets();
}
showFragment(mSysUiDirectManipulation);
}
- private void showNotificationExample(boolean hasFocus) {
- if (!hasFocus) {
- return; // do nothing if no focus.
- }
+ private void showNotificationExample() {
if (mNotificationFragment == null) {
mNotificationFragment = new HeadsUpNotificationFragment();
}
showFragment(mNotificationFragment);
}
- private void showScrollFragment(boolean hasFocus) {
- if (!hasFocus) {
- return; // Do nothing if no focus.
- }
+ private void showScrollFragment() {
if (mScrollFragment == null) {
mScrollFragment = new ScrollFragment();
}
showFragment(mScrollFragment);
}
+ private void showWebViewFragment() {
+ if (mWebViewFragment == null) {
+ mWebViewFragment = new WebViewFragment();
+ }
+ showFragment(mWebViewFragment);
+ }
+
+ private void showCustomFocusAreasFragment() {
+ if (mCustomFocusAreasFragment == null) {
+ mCustomFocusAreasFragment = new CustomFocusAreasFragment();
+ }
+ showFragment(mCustomFocusAreasFragment);
+ }
+
private void showFragment(Fragment fragment) {
getFragmentManager().beginTransaction()
.replace(R.id.rotary_content, fragment)
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/WebViewFragment.java b/RotaryPlayground/src/com/android/car/rotaryplayground/WebViewFragment.java
new file mode 100644
index 0000000..033b6a8
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/WebViewFragment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.rotaryplayground;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Base64;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.CheckBox;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Fragment to demo a layout with a {@link WebView}. */
+public class WebViewFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.rotary_web_view, container, false);
+ Button toggleButtonsButton = view.findViewById(R.id.toggle_buttons);
+ Button topButton = view.findViewById(R.id.top_button);
+ WebView webView = view.findViewById(R.id.web_view);
+ Button bottomButton = view.findViewById(R.id.bottom_button);
+
+ toggleButtonsButton.setOnClickListener(v -> {
+ int buttonVisibility =
+ topButton.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
+ topButton.setVisibility(buttonVisibility);
+ bottomButton.setVisibility(buttonVisibility);
+ });
+
+ Resources res = getResources();
+ InputStream inputStream = res.openRawResource(R.raw.web_view_html);
+ byte[] byteArray = new byte[0];
+ try {
+ byteArray = new byte[inputStream.available()];
+ inputStream.read(byteArray);
+ } catch (IOException e) {
+ Log.w("WebViewFragment", "Can't read HTML");
+ }
+ String webViewHtml = new String(byteArray);
+ String encodedHtml = Base64.encodeToString(webViewHtml.getBytes(), Base64.NO_PADDING);
+ webView.loadData(encodedHtml, "text/html", "base64");
+ return view;
+ }
+}
diff --git a/TestMediaApp/build.gradle b/TestMediaApp/build.gradle
index 79fd66d..6711d6d 100644
--- a/TestMediaApp/build.gradle
+++ b/TestMediaApp/build.gradle
@@ -18,11 +18,11 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 30
defaultConfig {
applicationId "com.android.car.media.testmediaapp"
- minSdkVersion 21
- targetSdkVersion 28
+ minSdkVersion 28
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
}
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
index 3c3eeb6..61b3a31 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
@@ -33,7 +33,9 @@ import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat;
import com.android.car.media.testmediaapp.loader.TmaLoader;
+import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs;
import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType;
+import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType;
import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaReplyDelay;
import com.android.car.media.testmediaapp.prefs.TmaPrefs;
@@ -72,6 +74,10 @@ public class TmaBrowser extends MediaBrowserServiceCompat {
private BrowserRoot mRoot;
+ public TmaBrowser() {
+ super();
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -91,14 +97,9 @@ public class TmaBrowser extends MediaBrowserServiceCompat {
mediaSessionExtras.putString(BROWSE_SERVICE_FOR_SESSION_KEY, TmaBrowser.class.getName());
mSession.setExtras(mediaSessionExtras);
- mPrefs.mAccountType.registerChangeListener(
- (oldValue, newValue) -> onAccountChanged(newValue));
-
- mPrefs.mRootNodeType.registerChangeListener(
- (oldValue, newValue) -> invalidateRoot());
-
- mPrefs.mRootReplyDelay.registerChangeListener(
- (oldValue, newValue) -> invalidateRoot());
+ mPrefs.mAccountType.registerChangeListener(mOnAccountChanged);
+ mPrefs.mRootNodeType.registerChangeListener(mOnRootNodeTypeChanged);
+ mPrefs.mRootReplyDelay.registerChangeListener(mOnReplyDelayChanged);
Bundle browserRootExtras = new Bundle();
browserRootExtras.putBoolean(SEARCH_SUPPORTED, true);
@@ -109,23 +110,33 @@ public class TmaBrowser extends MediaBrowserServiceCompat {
@Override
public void onDestroy() {
+ mPrefs.mAccountType.unregisterChangeListener(mOnAccountChanged);
+ mPrefs.mRootNodeType.unregisterChangeListener(mOnRootNodeTypeChanged);
+ mPrefs.mRootReplyDelay.unregisterChangeListener(mOnReplyDelayChanged);
mSession.release();
mHandler = null;
mPrefs = null;
super.onDestroy();
}
- private void onAccountChanged(TmaAccountType accountType) {
- if (PLAYBACK_STATE_UPDATE_FIRST.equals(mPrefs.mLoginEventOrder.getValue())) {
- updatePlaybackState(accountType);
- invalidateRoot();
- } else {
- invalidateRoot();
- (new Handler()).postDelayed(() -> {
- updatePlaybackState(accountType);
- }, 3000);
- }
- }
+ private final TmaPrefs.PrefValueChangedListener<TmaAccountType> mOnAccountChanged =
+ (oldValue, newValue) -> {
+ if (PLAYBACK_STATE_UPDATE_FIRST.equals(mPrefs.mLoginEventOrder.getValue())) {
+ updatePlaybackState(newValue);
+ invalidateRoot();
+ } else {
+ invalidateRoot();
+ (new Handler()).postDelayed(() -> {
+ updatePlaybackState(newValue);
+ }, 3000);
+ }
+ };
+
+ private final TmaPrefs.PrefValueChangedListener<TmaBrowseNodeType> mOnRootNodeTypeChanged =
+ (oldValue, newValue) -> invalidateRoot();
+
+ private final TmaPrefs.PrefValueChangedListener<TmaReplyDelay> mOnReplyDelayChanged =
+ (oldValue, newValue) -> invalidateRoot();
private void updatePlaybackState(TmaAccountType accountType) {
if (accountType == TmaAccountType.NONE) {
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java b/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
index 84f481f..014e81e 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
@@ -52,13 +52,21 @@ public class TmaLauncherActivity extends AppCompatActivity {
// Get the token for the MediaSession
MediaSessionCompat.Token token = mediaBrowser.getSessionToken();
- // Create a MediaControllerCompat
- MediaControllerCompat controller =
- new MediaControllerCompat(TmaLauncherActivity.this, token);
-
- // Save the controller
- MediaControllerCompat.setMediaController(
- TmaLauncherActivity.this, controller);
+ try {
+ // Create a MediaControllerCompat
+ MediaControllerCompat controller =
+ new MediaControllerCompat(TmaLauncherActivity.this, token);
+ // Save the controller
+ MediaControllerCompat.setMediaController(
+ TmaLauncherActivity.this, controller);
+ } catch (Exception ex) {
+ // ToDo: b/166328624 Workaround for an Android Studio Build error:
+ // unreported exception RemoteException
+ // Whereas as an Android Soong Build error:
+ // RemoteException is never thrown
+ Log.e(TAG, "Failed to create MediaControllerCompat", ex);
+ return;
+ }
}
};
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
index 8e9d89f..dd08cd2 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
@@ -110,6 +110,14 @@ public class TmaPrefs {
mSharedPrefs.registerOnSharedPreferenceChangeListener(listenerWrapper);
mListeners.put(listener, listenerWrapper);
}
+
+ public void unregisterChangeListener(PrefValueChangedListener<T> listener) {
+ OnSharedPreferenceChangeListener listenerWrapper = mListeners.get(listener);
+ if (listenerWrapper != null) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(listenerWrapper);
+ mListeners.remove(listener);
+ }
+ }
}
diff --git a/build.gradle b/build.gradle
index df49ba3..5493299 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,7 +22,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:4.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..5bac8ac
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ce751bb..e5a0e03 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Sep 26 14:52:51 PDT 2019
+#Mon Aug 24 13:36:33 PDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/readme.md b/readme.md
index 6b13001..6871ff1 100644
--- a/readme.md
+++ b/readme.md
@@ -1,16 +1,37 @@
# Car test apps
-This repository is only for car test applications.
+This repository is only for car test applications. They can be unbundled from Android devices.
+
+## Prerequisites
+
+* You need to build or have a [car-ui-lib](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:packages/apps/Car/libs/car-ui-lib/)
+aar ready first if to build test apps on Android Studio.
+* android-10.0.0_r30 is a release tag placeholder in this doc, you should replace the one you need.
## Building
-If you are not contributing to the repo, you can clone the repo via `git clone sso://googleplex-android/platform/packages/apps/Car/tests --branch pi-car-dev --single-branch`. Otherwise, see [workstation setup](#workstation-setup).
+1. There are 3 ways to get the source. Pick one works better for you.
+ * A: Download [tgz](https://android.googlesource.com/platform/packages/apps/Car/tests/+archive/refs/tags/android-10.0.0_r30.tar.gz)
+from the source if no plan to contribue.
+ * B: Repo workflow, see [workstation setup](#workstation-setup).
+ * C: Git workflow, e.g.
+```
+git clone -b $BRANCH https://android.googlesource.com/platform/packages/apps/Car/tests
+cd tests
+f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
+```
+
+* To learn more, checkout [Basic Gerrit Walkthrough](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough-github.html).
+* See tools/git_clone_projects.sh as an example to get both Car/libs and tests projects.
-Install [Android Studio](go/install-android-studio). Then import the `tests` Gradle project into Android Studio.
+2. Install [Android Studio](https://developer.android.com/studio), open the `tests`
+project by Android Studio and do your magic.
+ * You will need to build car-ui-lib by Android Studio first.
### TestMediaApp
-TestMediaApp should be one of the run configurations. The green Run button should build and install the app on your phone.
+TestMediaApp should be one of the run configurations. The green Run button should build and install
+the app on your phone.
To see TestMediaApp in Android Auto Projected:
@@ -24,31 +45,48 @@ To see TestMediaApp in Android Auto Projected:
### RotaryPlayground
-RotaryPlayground is a test and reference application for the AAOS Rotary framework to use with an external rotary input device.
+RotaryPlayground is a test and reference application for the AAOS Rotary framework to use with an
+external rotary input device.
-To buid and install RotaryPlayground into an AAOS device:
+Beside building in Android Studio, you can also build and install RotaryPlayground into an AAOS
+device:
```
$ make RotaryPlayground
$ adb install -r -g out/target/[path]/system/app/RotaryPlayground/RotaryPlayground.apk
```
+* See tools/go_rotary.sh for an example build, install and run the test app in an Android tree.
+
+
+### RotaryIME
+
+RotaryIME is a sample input method for rotary controllers.
+
+To build and install RotaryIME onto an AAOS device:
+```
+$ make RotaryIME
+$ adb install -r -g out/target/[path]/system/app/RotaryIME/RotaryIME.apk
+```
+
## Contributing
### Workstation setup
-Install [repo](https://source.android.com/setup/build/downloading#installing-repo) command line tool. Then run:
+Install [repo](https://source.android.com/setup/build/downloading#installing-repo) command line
+tool. Then run:
```
sudo apt-get install gitk
sudo apt-get install git-gui
mkdir WORKING_DIRECTORY_FOR_GIT_REPO
cd WORKING_DIRECTORY_FOR_GIT_REPO
-repo init -u persistent-https://googleplex-android.git.corp.google.com/platform/manifest -b pi-car-dev -g name:platform/tools/repohooks,name:platform/packages/apps/Car/tests --depth=1
+repo init -u https://android.googlesource.com/platform/manifest -b $BRANCH -g name:platform/tools/repohooks,name:platform/packages/apps/Car/tests --depth=1
repo sync
```
### Making a change
+#### Repo workflow
```
repo start BRANCH_NAME .
@@ -58,3 +96,10 @@ git gui &
repo upload .
```
+#### Git workflow
+```
+# Make some changes
+git add .
+git commit
+git push origin HEAD:refs/for/$BRANCH
+```
diff --git a/settings.gradle b/settings.gradle
index 38f6519..d3837c7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -14,4 +14,10 @@
* limitations under the License.
*/
+// You need to have car-ui-lib project in place. See tools/git_clone_projects.sh for example.
+include ':car-ui-lib'
+project(':car-ui-lib').projectDir= new File('../libs/car-ui-lib/car-ui-lib')
+
include ':TestMediaApp'
+include ':RotaryPlayground'
+include ':RotaryIME'
diff --git a/tools/git_clone_projects.sh b/tools/git_clone_projects.sh
new file mode 100755
index 0000000..80fbf5e
--- /dev/null
+++ b/tools/git_clone_projects.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+# Copyright (C) 2020 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.
+
+echo "An example to clone minimal git projects for the tests app development by Android Studio."
+
+if [[ -z $GIT_REPO_URL ]]; then
+ echo 'Error: you need to specify GIT_REPO_URL="target-url"'
+ exit
+fi
+echo "GIT_REPO_URL=$GIT_REPO_URL"
+
+if [[ -z $BRANCH ]]; then
+ echo 'Error: you need to specify BRANCH="target-branch"'
+ exit
+fi
+echo "BRANCH=$BRANCH"
+
+if [[ -z $WORK_DIR ]]; then
+ export WORK_DIR="$PWD/Car"
+fi
+echo "WORK_DIR=$WORK_DIR"
+
+mkdir -p $WORK_DIR
+cd $WORK_DIR
+
+PROJECTS=0
+SECONDS=0
+echo "Cloning Car/libs"
+git clone -b $BRANCH "$GIT_REPO_URL/platform/packages/apps/Car/libs"
+let "PROJECTS++"
+cd "$WORK_DIR/libs"
+f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
+cd $WORK_DIR
+echo
+
+echo "Cloning Car/libs"
+git clone -b $BRANCH "$GIT_REPO_URL/platform/packages/apps/Car/tests"
+let "PROJECTS++"
+cd "$WORK_DIR/tests"
+f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
+cd $WORK_DIR
+echo
+
+ls -l "$WORK_DIR"
+
+echo "
+
+Cloning $PROJECTS projects takes: $SECONDS sec.
+
+Do your magic and then get the change pushed for review, e.g.:
+git add -u
+git commit
+git push origin HEAD:refs/for/$BRANCH
+"
diff --git a/tools/go_rotary.sh b/tools/go_rotary.sh
new file mode 100755
index 0000000..b3b28d3
--- /dev/null
+++ b/tools/go_rotary.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+# Copyright (C) 2020 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.
+
+TMP_OUTDIR="/tmp/rotary"
+ME=`basename "$0"`
+
+function help {
+ echo "A simple helper script that runs the Trade Federation unit tests"
+ echo "to print this message: packages/apps/Car/tests/tools/$ME"
+ echo "to build: packages/apps/Car/tests/tools/$ME b"
+ echo "to install: packages/apps/Car/tests/tools/$ME i"
+ echo "to run only: packages/apps/Car/tests/tools/$ME r"
+ echo "the apks and jar are in $TMP_OUTDIR"
+}
+
+function build {
+ echo
+ echo "Building the apks"
+ . build/envsetup.sh ; lunch aosp_car_x86-userdebug; make CarRotaryController RotaryPlayground android.car -j32
+ ANDROID_OUT=$ANDROID_BUILD_TOP/out
+ rm -r $TMP_OUTDIR
+ mkdir -p $TMP_OUTDIR
+ cp $ANDROID_PRODUCT_OUT/system/app/CarRotaryController/CarRotaryController.apk $TMP_OUTDIR
+ cp $ANDROID_PRODUCT_OUT/system/app/RotaryPlayground/RotaryPlayground.apk $TMP_OUTDIR
+ cp $ANDROID_OUT/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/classes.jar $TMP_OUTDIR/android.car.jar
+}
+
+function install {
+ echo
+ echo "Installing the apks"
+ adb install -g $TMP_OUTDIR/CarRotaryController.apk
+ adb install -g $TMP_OUTDIR/RotaryPlayground.apk
+}
+
+function run {
+ echo
+ echo "Starting Rotary service and playground app"
+ adb shell settings put secure enabled_accessibility_services com.android.car.rotary/com.android.car.rotary.RotaryService
+ adb shell am start -n com.android.car.rotaryplayground/com.android.car.rotaryplayground.RotaryActivity
+}
+
+ACTION=$1
+
+if [[ $ACTION == "b" ]]; then
+ SECONDS=0
+ build
+ echo "Build time: $SECONDS sec."
+ ACTION="i"
+fi
+
+if [[ $ACTION == "i" ]]; then
+ install
+ ACTION="r"
+fi
+
+if [[ $ACTION == "r" ]]; then
+ run
+ exit
+fi
+
+help