diff options
author | Xin Li <delphij@google.com> | 2020-11-23 13:14:25 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-11-23 13:14:25 -0800 |
commit | 1690020f707b9763e8be93236e79acbf0492f970 (patch) | |
tree | 0ce45912b84db71690c634c61229d72d6d18c460 | |
parent | 8a1d8d0b3281d0123265db548586e9d916d6c7b6 (diff) | |
parent | fe2d325bfaa3d2819cef65bffa97f2a03300bf17 (diff) | |
download | tests-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
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 @@ -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 |