diff options
author | Danny Epstein <depstein@google.com> | 2020-06-30 19:00:40 -0700 |
---|---|---|
committer | Danny Epstein <depstein@google.com> | 2020-07-06 10:57:13 -0700 |
commit | 577fab74809d3ea85e09dd586036f18ec795be57 (patch) | |
tree | da2131ef05122d5d585c4c8f5d2ae70e0f9edb2e /RotaryIME | |
parent | 49aed455d306f0d487b146648acb561fe3795710 (diff) | |
download | tests-577fab74809d3ea85e09dd586036f18ec795be57.tar.gz |
Rotary IME
A demo rotary IME.
Bug: 131421840
Bug: 152439508
Test: manual
Change-Id: I81e943ca0bdee9b8b83b67c8be884d8392e9aa10
Diffstat (limited to 'RotaryIME')
-rw-r--r-- | RotaryIME/Android.bp | 35 | ||||
-rw-r--r-- | RotaryIME/AndroidManifest.xml | 31 | ||||
-rw-r--r-- | RotaryIME/readme.md | 25 | ||||
-rw-r--r-- | RotaryIME/res/color/key_background.xml | 21 | ||||
-rw-r--r-- | RotaryIME/res/layout/horizontal_keyboard.xml | 196 | ||||
-rw-r--r-- | RotaryIME/res/values/colors.xml | 21 | ||||
-rw-r--r-- | RotaryIME/res/values/strings.xml | 67 | ||||
-rw-r--r-- | RotaryIME/res/values/styles.xml | 28 | ||||
-rw-r--r-- | RotaryIME/res/xml/ime.xml | 22 | ||||
-rw-r--r-- | RotaryIME/src/com/android/car/rotaryime/RotaryIme.java | 181 |
10 files changed, 627 insertions, 0 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..f76b6b4 --- /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="#2c2c2c"> + <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..e2bc73d --- /dev/null +++ b/RotaryIME/res/values/colors.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. + --> +<resources> + <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..4d7f3e4 --- /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 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); + } + } + } +} |