From 305b1d4e337a5076d092a53569a88aade26ea339 Mon Sep 17 00:00:00 2001 From: Yuriy Liskov Date: Sat, 20 Jan 2018 23:25:33 +0200 Subject: [PATCH] initial commit --- .gitignore | 22 + README.md | 3 + build.gradle | 40 + gradle.properties | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++ gradlew.bat | 90 ++ leankeykeyboard/.gitignore | 1 + leankeykeyboard/build.gradle | 32 + leankeykeyboard/proguard-rules.pro | 25 + .../ExampleInstrumentedTest.java | 26 + leankeykeyboard/src/main/AndroidManifest.xml | 33 + .../com/anysoftkeyboard/addons/AddOn.java | 48 + .../com/anysoftkeyboard/addons/AddOnImpl.java | 131 ++ .../anysoftkeyboard/addons/AddOnsFactory.java | 373 +++++ .../com/anysoftkeyboard/addons/Support.java | 64 + .../keyboards/KeyboardAddOnAndBuilder.java | 89 ++ .../keyboards/KeyboardFactory.java | 137 ++ .../anysoftkeyboard/utils/BuildConfig.java | 6 + .../utils/LogCatLogProvider.java | 48 + .../anysoftkeyboard/utils/LogProvider.java | 34 + .../com/anysoftkeyboard/utils/Logger.java | 227 +++ .../utils/NullLogProvider.java | 39 + .../com/anysoftkeyboard/utils/XmlUtils.java | 825 +++++++++++ .../com/anysoftkeyboard/utils/XmlWriter.java | 254 ++++ .../android/leanback/ime/EventLogTags.java | 16 + .../ime/LeanbackKeyboardContainer.java | 1302 +++++++++++++++++ .../ime/LeanbackKeyboardController.java | 822 +++++++++++ .../leanback/ime/LeanbackKeyboardView.java | 508 +++++++ .../android/leanback/ime/LeanbackLocales.java | 92 ++ .../ime/LeanbackSuggestionsFactory.java | 94 ++ .../android/leanback/ime/LeanbackUtils.java | 38 + .../ime/voice/BitmapSoundLevelView.java | 195 +++ .../leanback/ime/voice/RecognizerView.java | 216 +++ .../leanback/ime/voice/SpeechLevelSource.java | 25 + .../pano/util/TouchNavMotionTracker.java | 164 +++ .../pano/util/TouchNavSpaceTracker.java | 593 ++++++++ .../leanback/ime/LeanbackImeService.java | 305 ++++ .../inputchooser/LaunchAppActivity.java | 77 + .../inputchooser/RestartServiceReceiver.java | 77 + .../keyboardaddons/KeyboardManager.java | 55 + .../drawable-hdpi-v4/ic_ime_accent_close.png | Bin 0 -> 360 bytes .../res/drawable-hdpi-v4/ic_ime_alphabet.png | Bin 0 -> 975 bytes .../res/drawable-hdpi-v4/ic_ime_delete.png | Bin 0 -> 874 bytes .../drawable-hdpi-v4/ic_ime_left_arrow.png | Bin 0 -> 311 bytes .../drawable-hdpi-v4/ic_ime_right_arrow.png | Bin 0 -> 306 bytes .../drawable-hdpi-v4/ic_ime_shift_lock_on.png | Bin 0 -> 679 bytes .../res/drawable-hdpi-v4/ic_ime_shift_off.png | Bin 0 -> 748 bytes .../res/drawable-hdpi-v4/ic_ime_shift_on.png | Bin 0 -> 541 bytes .../res/drawable-hdpi-v4/ic_ime_space.png | Bin 0 -> 2215 bytes .../res/drawable-hdpi-v4/ic_ime_space_ru.png | Bin 0 -> 2324 bytes .../res/drawable-hdpi-v4/ic_ime_symbols.png | Bin 0 -> 957 bytes .../drawable-hdpi-v4/ic_voice_available.png | Bin 0 -> 6857 bytes .../res/drawable-hdpi-v4/ic_voice_focus.png | Bin 0 -> 7217 bytes .../res/drawable-hdpi-v4/ic_voice_off.png | Bin 0 -> 6877 bytes .../drawable-hdpi-v4/ic_voice_recording.png | Bin 0 -> 6156 bytes .../res/drawable-hdpi-v4/key_selector.9.png | Bin 0 -> 475 bytes .../res/drawable-hdpi-v4/touch_selector.9.png | Bin 0 -> 545 bytes .../res/drawable-hdpi-v4/vs_reactive_dark.png | Bin 0 -> 4388 bytes .../drawable-hdpi-v4/vs_reactive_light.png | Bin 0 -> 4085 bytes .../drawable-mdpi-v4/ic_ime_accent_close.png | Bin 0 -> 342 bytes .../res/drawable-mdpi-v4/ic_ime_alphabet.png | Bin 0 -> 717 bytes .../res/drawable-mdpi-v4/ic_ime_delete.png | Bin 0 -> 707 bytes .../drawable-mdpi-v4/ic_ime_left_arrow.png | Bin 0 -> 271 bytes .../drawable-mdpi-v4/ic_ime_right_arrow.png | Bin 0 -> 265 bytes .../drawable-mdpi-v4/ic_ime_shift_lock_on.png | Bin 0 -> 548 bytes .../res/drawable-mdpi-v4/ic_ime_shift_off.png | Bin 0 -> 532 bytes .../res/drawable-mdpi-v4/ic_ime_shift_on.png | Bin 0 -> 400 bytes .../res/drawable-mdpi-v4/ic_ime_space.png | Bin 0 -> 2215 bytes .../res/drawable-mdpi-v4/ic_ime_space_ru.png | Bin 0 -> 2324 bytes .../res/drawable-mdpi-v4/ic_ime_symbols.png | Bin 0 -> 642 bytes .../drawable-mdpi-v4/ic_voice_available.png | Bin 0 -> 4181 bytes .../res/drawable-mdpi-v4/ic_voice_focus.png | Bin 0 -> 4271 bytes .../res/drawable-mdpi-v4/ic_voice_off.png | Bin 0 -> 4177 bytes .../drawable-mdpi-v4/ic_voice_recording.png | Bin 0 -> 3775 bytes .../res/drawable-mdpi-v4/key_selector.9.png | Bin 0 -> 345 bytes .../res/drawable-mdpi-v4/touch_selector.9.png | Bin 0 -> 399 bytes .../res/drawable-mdpi-v4/vs_reactive_dark.png | Bin 0 -> 2825 bytes .../drawable-mdpi-v4/vs_reactive_light.png | Bin 0 -> 2160 bytes .../main/res/drawable-xhdpi-v4/banner_app.png | Bin 0 -> 20565 bytes .../drawable-xhdpi-v4/ic_ime_accent_close.png | Bin 0 -> 536 bytes .../res/drawable-xhdpi-v4/ic_ime_alphabet.png | Bin 0 -> 1390 bytes .../res/drawable-xhdpi-v4/ic_ime_delete.png | Bin 0 -> 1113 bytes .../drawable-xhdpi-v4/ic_ime_left_arrow.png | Bin 0 -> 376 bytes .../drawable-xhdpi-v4/ic_ime_right_arrow.png | Bin 0 -> 364 bytes .../ic_ime_shift_lock_on.png | Bin 0 -> 729 bytes .../drawable-xhdpi-v4/ic_ime_shift_off.png | Bin 0 -> 901 bytes .../res/drawable-xhdpi-v4/ic_ime_shift_on.png | Bin 0 -> 584 bytes .../res/drawable-xhdpi-v4/ic_ime_space.png | Bin 0 -> 2215 bytes .../res/drawable-xhdpi-v4/ic_ime_space_ru.png | Bin 0 -> 2324 bytes .../res/drawable-xhdpi-v4/ic_ime_symbols.png | Bin 0 -> 1187 bytes .../res/drawable-xhdpi-v4/ic_launcher.png | Bin 0 -> 5687 bytes .../drawable-xhdpi-v4/ic_voice_available.png | Bin 0 -> 8966 bytes .../res/drawable-xhdpi-v4/ic_voice_focus.png | Bin 0 -> 9382 bytes .../res/drawable-xhdpi-v4/ic_voice_off.png | Bin 0 -> 8982 bytes .../drawable-xhdpi-v4/ic_voice_recording.png | Bin 0 -> 8150 bytes .../res/drawable-xhdpi-v4/key_selector.9.png | Bin 0 -> 670 bytes .../drawable-xhdpi-v4/touch_selector.9.png | Bin 0 -> 711 bytes .../drawable-xhdpi-v4/vs_reactive_dark.png | Bin 0 -> 5769 bytes .../drawable-xhdpi-v4/vs_reactive_light.png | Bin 0 -> 5637 bytes .../res/drawable-xxhdpi-v4/banner_app.png | Bin 0 -> 20565 bytes .../ic_ime_accent_close.png | Bin 0 -> 439 bytes .../drawable-xxhdpi-v4/ic_ime_alphabet.png | Bin 0 -> 548 bytes .../res/drawable-xxhdpi-v4/ic_ime_delete.png | Bin 0 -> 1280 bytes .../drawable-xxhdpi-v4/ic_ime_left_arrow.png | Bin 0 -> 298 bytes .../drawable-xxhdpi-v4/ic_ime_right_arrow.png | Bin 0 -> 297 bytes .../ic_ime_shift_lock_on.png | Bin 0 -> 586 bytes .../drawable-xxhdpi-v4/ic_ime_shift_off.png | Bin 0 -> 717 bytes .../drawable-xxhdpi-v4/ic_ime_shift_on.png | Bin 0 -> 465 bytes .../res/drawable-xxhdpi-v4/ic_ime_space.png | Bin 0 -> 2215 bytes .../drawable-xxhdpi-v4/ic_ime_space_ru.png | Bin 0 -> 2324 bytes .../res/drawable-xxhdpi-v4/ic_ime_symbols.png | Bin 0 -> 493 bytes .../res/drawable-xxhdpi-v4/ic_launcher.png | Bin 0 -> 5687 bytes .../res/drawable-xxhdpi-v4/key_selector.9.png | Bin 0 -> 1129 bytes .../drawable-xxhdpi-v4/touch_selector.9.png | Bin 0 -> 1244 bytes .../main/res/drawable/selector_caps_shift.xml | 6 + .../res/drawable/vs_micbtn_off_selector.xml | 6 + .../res/drawable/vs_micbtn_on_selector.xml | 6 + .../res/drawable/vs_micbtn_rec_selector.xml | 7 + .../src/main/res/layout/candidate.xml | 5 + .../src/main/res/layout/input_leanback.xml | 15 + .../src/main/res/layout/recognizer_view.xml | 8 + .../src/main/res/layout/root_leanback.xml | 24 + .../src/main/res/layout/selector.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4208 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2555 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6114 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10056 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14696 bytes .../src/main/res/values/arrays.xml | 15 + .../res/values/bitmap_sound_level_view.xml | 16 + .../src/main/res/values/colors.xml | 12 + .../src/main/res/values/dimens.xml | 25 + .../src/main/res/values/fractions.xml | 7 + .../src/main/res/values/integers.xml | 7 + .../res/values/leanback_keyboard_view.xml | 7 + .../src/main/res/values/strings.xml | 26 + leankeykeyboard/src/main/res/xml/accent_a.xml | 17 + leankeykeyboard/src/main/res/xml/accent_c.xml | 10 + leankeykeyboard/src/main/res/xml/accent_d.xml | 8 + leankeykeyboard/src/main/res/xml/accent_e.xml | 14 + leankeykeyboard/src/main/res/xml/accent_g.xml | 9 + leankeykeyboard/src/main/res/xml/accent_i.xml | 13 + leankeykeyboard/src/main/res/xml/accent_k.xml | 8 + leankeykeyboard/src/main/res/xml/accent_l.xml | 9 + leankeykeyboard/src/main/res/xml/accent_n.xml | 10 + leankeykeyboard/src/main/res/xml/accent_o.xml | 16 + leankeykeyboard/src/main/res/xml/accent_s.xml | 11 + leankeykeyboard/src/main/res/xml/accent_t.xml | 8 + leankeykeyboard/src/main/res/xml/accent_u.xml | 14 + leankeykeyboard/src/main/res/xml/accent_y.xml | 8 + leankeykeyboard/src/main/res/xml/accent_z.xml | 10 + leankeykeyboard/src/main/res/xml/azerty.xml | 63 + leankeykeyboard/src/main/res/xml/method.xml | 4 + leankeykeyboard/src/main/res/xml/number.xml | 28 + .../src/main/res/xml/popup_symbols.xml | 4 + .../src/main/res/xml/qwerty_az.xml | 63 + .../src/main/res/xml/qwerty_ca.xml | 63 + .../src/main/res/xml/qwerty_da.xml | 63 + .../src/main/res/xml/qwerty_en_gb.xml | 63 + .../src/main/res/xml/qwerty_en_in.xml | 63 + .../src/main/res/xml/qwerty_es_eu.xml | 63 + .../src/main/res/xml/qwerty_es_us.xml | 63 + .../src/main/res/xml/qwerty_et.xml | 63 + .../src/main/res/xml/qwerty_eu.xml | 63 + .../src/main/res/xml/qwerty_fi.xml | 63 + .../src/main/res/xml/qwerty_leanback_ru.xml | 63 + .../src/main/res/xml/qwerty_nb.xml | 63 + .../src/main/res/xml/qwerty_sv.xml | 63 + .../src/main/res/xml/qwerty_us.xml | 63 + leankeykeyboard/src/main/res/xml/qwertz.xml | 63 + .../src/main/res/xml/qwertz_ch.xml | 63 + leankeykeyboard/src/main/res/xml/shift_0.xml | 7 + leankeykeyboard/src/main/res/xml/shift_1.xml | 7 + leankeykeyboard/src/main/res/xml/shift_2.xml | 7 + leankeykeyboard/src/main/res/xml/shift_3.xml | 7 + leankeykeyboard/src/main/res/xml/shift_4.xml | 7 + .../src/main/res/xml/shift_4_en_gb.xml | 7 + .../src/main/res/xml/shift_4_en_in.xml | 7 + .../src/main/res/xml/shift_4_eu.xml | 7 + leankeykeyboard/src/main/res/xml/shift_5.xml | 7 + leankeykeyboard/src/main/res/xml/shift_6.xml | 7 + leankeykeyboard/src/main/res/xml/shift_7.xml | 7 + leankeykeyboard/src/main/res/xml/shift_8.xml | 7 + leankeykeyboard/src/main/res/xml/shift_9.xml | 7 + leankeykeyboard/src/main/res/xml/shift_aa.xml | 7 + leankeykeyboard/src/main/res/xml/shift_ae.xml | 7 + leankeykeyboard/src/main/res/xml/shift_ao.xml | 7 + leankeykeyboard/src/main/res/xml/shift_b.xml | 7 + leankeykeyboard/src/main/res/xml/shift_cc.xml | 7 + leankeykeyboard/src/main/res/xml/shift_f.xml | 7 + leankeykeyboard/src/main/res/xml/shift_h.xml | 7 + leankeykeyboard/src/main/res/xml/shift_j.xml | 7 + leankeykeyboard/src/main/res/xml/shift_m.xml | 7 + .../src/main/res/xml/shift_n_es.xml | 7 + .../src/main/res/xml/shift_nn_es.xml | 7 + leankeykeyboard/src/main/res/xml/shift_oo.xml | 7 + leankeykeyboard/src/main/res/xml/shift_ox.xml | 7 + leankeykeyboard/src/main/res/xml/shift_p.xml | 7 + leankeykeyboard/src/main/res/xml/shift_q.xml | 7 + leankeykeyboard/src/main/res/xml/shift_r.xml | 7 + leankeykeyboard/src/main/res/xml/shift_uu.xml | 7 + leankeykeyboard/src/main/res/xml/shift_v.xml | 7 + leankeykeyboard/src/main/res/xml/shift_w.xml | 7 + leankeykeyboard/src/main/res/xml/shift_x.xml | 7 + leankeykeyboard/src/main/res/xml/shift_y.xml | 7 + .../src/main/res/xml/sym_azerty.xml | 63 + .../src/main/res/xml/sym_en_gb.xml | 63 + .../src/main/res/xml/sym_en_in.xml | 63 + leankeykeyboard/src/main/res/xml/sym_eu.xml | 63 + leankeykeyboard/src/main/res/xml/sym_fr.xml | 63 + leankeykeyboard/src/main/res/xml/sym_us.xml | 63 + .../leankeykeyboard/ExampleUnitTest.java | 17 + settings.gradle | 1 + 220 files changed, 9408 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 leankeykeyboard/.gitignore create mode 100644 leankeykeyboard/build.gradle create mode 100644 leankeykeyboard/proguard-rules.pro create mode 100644 leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java create mode 100644 leankeykeyboard/src/main/AndroidManifest.xml create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/keyboards/KeyboardAddOnAndBuilder.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/keyboards/KeyboardFactory.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java create mode 100644 leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java create mode 100644 leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java create mode 100644 leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_left_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_right_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_lock_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space_ru.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_recording.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/key_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/touch_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_dark.png create mode 100644 leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_accent_close.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_left_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_lock_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space_ru.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_recording.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png create mode 100644 leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_accent_close.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_right_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_lock_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space_ru.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_available.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_focus.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_recording.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/touch_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_dark.png create mode 100644 leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/banner_app.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_accent_close.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_lock_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space_ru.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/key_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png create mode 100644 leankeykeyboard/src/main/res/drawable/selector_caps_shift.xml create mode 100644 leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml create mode 100644 leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml create mode 100644 leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml create mode 100644 leankeykeyboard/src/main/res/layout/candidate.xml create mode 100644 leankeykeyboard/src/main/res/layout/input_leanback.xml create mode 100644 leankeykeyboard/src/main/res/layout/recognizer_view.xml create mode 100644 leankeykeyboard/src/main/res/layout/root_leanback.xml create mode 100644 leankeykeyboard/src/main/res/layout/selector.xml create mode 100644 leankeykeyboard/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 leankeykeyboard/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 leankeykeyboard/src/main/res/values/arrays.xml create mode 100644 leankeykeyboard/src/main/res/values/bitmap_sound_level_view.xml create mode 100644 leankeykeyboard/src/main/res/values/colors.xml create mode 100644 leankeykeyboard/src/main/res/values/dimens.xml create mode 100644 leankeykeyboard/src/main/res/values/fractions.xml create mode 100644 leankeykeyboard/src/main/res/values/integers.xml create mode 100644 leankeykeyboard/src/main/res/values/leanback_keyboard_view.xml create mode 100644 leankeykeyboard/src/main/res/values/strings.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_a.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_c.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_d.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_e.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_g.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_i.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_k.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_l.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_n.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_o.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_s.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_t.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_u.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_y.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_z.xml create mode 100644 leankeykeyboard/src/main/res/xml/azerty.xml create mode 100644 leankeykeyboard/src/main/res/xml/method.xml create mode 100644 leankeykeyboard/src/main/res/xml/number.xml create mode 100644 leankeykeyboard/src/main/res/xml/popup_symbols.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_az.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_ca.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_da.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_en_gb.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_en_in.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_es_eu.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_es_us.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_et.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_eu.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_fi.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_leanback_ru.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_nb.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_sv.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_us.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwertz.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwertz_ch.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_0.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_1.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_2.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_3.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_4.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_4_en_gb.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_4_en_in.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_4_eu.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_5.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_6.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_7.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_8.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_9.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_aa.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_ae.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_ao.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_b.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_cc.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_f.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_h.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_j.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_m.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_n_es.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_nn_es.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_oo.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_ox.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_p.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_q.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_r.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_uu.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_v.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_w.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_x.xml create mode 100644 leankeykeyboard/src/main/res/xml/shift_y.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_azerty.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_en_gb.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_en_in.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_eu.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_fr.xml create mode 100644 leankeykeyboard/src/main/res/xml/sym_us.xml create mode 100644 leankeykeyboard/src/test/java/com/liskovsoft/leankeykeyboard/ExampleUnitTest.java create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47c91b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +notes.txt +/files +/misc +/TODO.txt +tmp/ +*_bak* +*_tmp +*.bak* +/.idea +*.apk +*.7z +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild +fabric.properties +*BAK.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea691bc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# LeanKey Keyboard sources + +Original app can be found on the [Google Play](https://play.google.com/store/apps/details?id=org.liskovsoft.androidtv.rukeyboard) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..353b1a6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } + // WARNING: don't use 'project.ext' because IDE 'Cannot infer argument type' + // https://stackoverflow.com/questions/20404476/how-to-define-common-android-properties-for-all-modules-using-gradle + // Gradle constants example: https://github.com/google/ExoPlayer + ext { + compileSdkVersion = 27 + buildToolsVersion = "27.0.3" + minSdkVersion = 14 + targetSdkVersion = 27 + appCompatVersion = 'com.android.support:appcompat-v7:27.+' + espressoCoreVersion = 'com.android.support.test.espresso:espresso-core:2.2.2' + junitVersion = 'junit:junit:4.12' + supportVersion = 'com.android.support:support-v4:27.+' + robolectricVersion = 'org.robolectric:robolectric:3.5.1' + crashlyticsVersion = 'com.crashlytics.sdk.android:crashlytics:2.8.0@aar' + } +} + +allprojects { + repositories { + jcenter() + // com.android.support libs + maven { url 'https://maven.google.com' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..d9ae40f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9a778d6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/leankeykeyboard/.gitignore b/leankeykeyboard/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/leankeykeyboard/.gitignore @@ -0,0 +1 @@ +/build diff --git a/leankeykeyboard/build.gradle b/leankeykeyboard/build.gradle new file mode 100644 index 0000000..601fcae --- /dev/null +++ b/leankeykeyboard/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion project.properties.compileSdkVersion + buildToolsVersion project.properties.buildToolsVersion + + defaultConfig { + applicationId "com.liskovsoft.leankeykeyboard" + minSdkVersion project.properties.minSdkVersion + targetSdkVersion project.properties.targetSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile(project.properties.espressoCoreVersion, { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile project.properties.appCompatVersion + testCompile project.properties.junitVersion +} diff --git a/leankeykeyboard/proguard-rules.pro b/leankeykeyboard/proguard-rules.pro new file mode 100644 index 0000000..57bc3d6 --- /dev/null +++ b/leankeykeyboard/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\portable\dev\android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java b/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java new file mode 100644 index 0000000..fe154da --- /dev/null +++ b/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.liskovsoft.leankeykeyboard; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.liskovsoft.leankeykeyboard", appContext.getPackageName()); + } +} diff --git a/leankeykeyboard/src/main/AndroidManifest.xml b/leankeykeyboard/src/main/AndroidManifest.xml new file mode 100644 index 0000000..18b3fef --- /dev/null +++ b/leankeykeyboard/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java new file mode 100644 index 0000000..706e908 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public interface AddOn { + interface AddOnResourceMapping { + /*@AttrRes + int getLocalAttrIdFromRemote(@AttrRes int remoteAttributeResourceId);*/ + + int[] getRemoteStyleableArrayFromLocal(int[] localStyleableArray); + } + + int INVALID_RES_ID = 0; + + String getId(); + + String getName(); + + String getDescription(); + + String getPackageName(); + + @Nullable + Context getPackageContext(); + + int getSortIndex(); + + @NonNull + AddOnResourceMapping getResourceMapping(); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java new file mode 100644 index 0000000..1c9f08c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.SparseArrayCompat; +import android.util.SparseIntArray; + +import com.anysoftkeyboard.utils.Logger; + +import java.lang.ref.WeakReference; +import java.util.Arrays; + +public abstract class AddOnImpl implements AddOn { + + private static final String TAG = "ASK_AddOnImpl"; + private final String mId; + private final String mName; + private final String mDescription; + private final String mPackageName; + private final Context mAskAppContext; + private WeakReference mPackageContext; + private final int mSortIndex; + private final AddOnResourceMappingImpl mAddOnResourceMapping; + + protected AddOnImpl(Context askContext, Context packageContext, String id, int nameResId, + String description, int sortIndex) { + mId = id; + mAskAppContext = askContext; + mName = packageContext.getString(nameResId); + mDescription = description; + mPackageName = packageContext.getPackageName(); + mPackageContext = new WeakReference<>(packageContext); + mSortIndex = sortIndex; + mAddOnResourceMapping = new AddOnResourceMappingImpl(this); + } + + public final String getId() { + return mId; + } + + public final String getDescription() { + return mDescription; + } + + public String getPackageName() { + return mPackageName; + } + + @Nullable + public final Context getPackageContext() { + Context c = mPackageContext.get(); + if (c == null) { + try { + c = mAskAppContext.createPackageContext(mPackageName, Context.CONTEXT_IGNORE_SECURITY); + mPackageContext = new WeakReference<>(c); + } catch (NameNotFoundException e) { + Logger.w(TAG, "Failed to find package %s!", mPackageName); + Logger.w(TAG, "Failed to find package! ", e); + } + } + return c; + } + + public final int getSortIndex() { + return mSortIndex; + } + + public String getName() { + return mName; + } + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof AddOn && + ((AddOn) o).getId().equals(getId()); + } + + @NonNull + @Override + public AddOnResourceMapping getResourceMapping() { + return mAddOnResourceMapping; + } + + private static class AddOnResourceMappingImpl implements AddOnResourceMapping { + private final WeakReference mAddOnWeakReference; + private final SparseIntArray mAttributesMapping = new SparseIntArray(); + private final SparseArrayCompat mStyleableArrayMapping = new SparseArrayCompat<>(); + + private AddOnResourceMappingImpl(@NonNull AddOnImpl addOn) { + mAddOnWeakReference = new WeakReference<>(addOn); + } + + @Override + public int[] getRemoteStyleableArrayFromLocal(int[] localStyleableArray) { + int localStyleableId = Arrays.hashCode(localStyleableArray); + int indexOfRemoteArray = mStyleableArrayMapping.indexOfKey(localStyleableId); + if (indexOfRemoteArray >= 0) return mStyleableArrayMapping.valueAt(indexOfRemoteArray); + AddOnImpl addOn = mAddOnWeakReference.get(); + if (addOn == null) return new int[0]; + Context askContext = addOn.mAskAppContext; + Context remoteContext = addOn.getPackageContext(); + if (remoteContext == null) return new int[0]; + int[] remoteAttrIds = Support.createBackwardCompatibleStyleable(localStyleableArray, askContext, remoteContext, mAttributesMapping); + mStyleableArrayMapping.put(localStyleableId, remoteAttrIds); + return remoteAttrIds; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java new file mode 100644 index 0000000..81c90ac --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.util.AttributeSet; +import android.util.Xml; + +//import com.anysoftkeyboard.AnySoftKeyboard; +import com.anysoftkeyboard.utils.Logger; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +public abstract class AddOnsFactory { + + private static final class AddOnsComparator implements Comparator { + private final String mAskPackageName; + + private AddOnsComparator(Context askContext) { + mAskPackageName = askContext.getPackageName(); + } + + public int compare(AddOn k1, AddOn k2) { + String c1 = k1.getPackageName(); + String c2 = k2.getPackageName(); + + if (c1.equals(c2)) + return k1.getSortIndex() - k2.getSortIndex(); + else if (c1.equals(mAskPackageName))//I want to make sure ASK packages are first + return -1; + else if (c2.equals(mAskPackageName)) + return 1; + else + return c1.compareToIgnoreCase(c2); + } + } + + private final static ArrayList> mActiveInstances = new ArrayList<>(); + + private static final String sTAG = "AddOnsFactory"; + + //public static void onPackageChanged(final Intent eventIntent, final AnySoftKeyboard ask) { + // boolean cleared = false; + // boolean recreateView = false; + // for (AddOnsFactory factory : mActiveInstances) { + // try { + // if (factory.isEventRequiresCacheRefresh(eventIntent, ask.getApplicationContext())) { + // cleared = true; + // if (factory.isEventRequiresViewReset(eventIntent, ask.getApplicationContext())) recreateView = true; + // Logger.d(sTAG, factory.getClass().getName() + " will handle this package-changed event. Also recreate view? " + recreateView); + // factory.clearAddOnList(); + // } + // } catch (NameNotFoundException e) { + // e.printStackTrace(); + // } + // } + // if (cleared) ask.resetKeyboardView(recreateView); + //} + + public static AddOn locateAddOn(String id, Context askContext) { + for (AddOnsFactory factory : mActiveInstances) { + AddOn addOn = factory.getAddOnById(id, askContext); + if (addOn != null) { + Logger.d(sTAG, "Located addon with id " + addOn.getId() + " of type " + addOn.getClass().getName()); + return addOn; + } + } + + return null; + } + + protected final String TAG; + + /** + * This is the interface name that a broadcast receiver implementing an + * external addon should say that it supports -- that is, this is the + * action it uses for its intent filter. + */ + private final String RECEIVER_INTERFACE; + + /** + * Name under which an external addon broadcast receiver component + * publishes information about itself. + */ + private final String RECEIVER_META_DATA; + + private final ArrayList mAddOns = new ArrayList<>(); + private final HashMap mAddOnsById = new HashMap<>(); + + private final boolean mReadExternalPacksToo; + private final String ROOT_NODE_TAG; + private final String ADDON_NODE_TAG; + //private final int mBuildInAddOnsResId; + + private static final String XML_PREF_ID_ATTRIBUTE = "id"; + private static final String XML_NAME_RES_ID_ATTRIBUTE = "nameResId"; + private static final String XML_DESCRIPTION_ATTRIBUTE = "description"; + private static final String XML_SORT_INDEX_ATTRIBUTE = "index"; + + protected AddOnsFactory(String tag, String receiverInterface, String receiverMetaData, String rootNodeTag, String addonNodeTag, int buildInAddonResId, boolean readExternalPacksToo) { + TAG = tag; + RECEIVER_INTERFACE = receiverInterface; + RECEIVER_META_DATA = receiverMetaData; + ROOT_NODE_TAG = rootNodeTag; + ADDON_NODE_TAG = addonNodeTag; + //mBuildInAddOnsResId = buildInAddonResId; + mReadExternalPacksToo = readExternalPacksToo; + + mActiveInstances.add(this); + } + + protected boolean isEventRequiresCacheRefresh(Intent eventIntent, Context context) { + String action = eventIntent.getAction(); + String packageNameSchemePart = eventIntent.getData().getSchemeSpecificPart(); + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + //will reset only if the new package has my addons + boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); + if (hasAddon) { + Logger.d(TAG, "It seems that an addon exists in a newly installed package " + packageNameSchemePart + ". I need to reload stuff."); + return true; + } + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + //If I'm managing OR it contains an addon (could be new feature in the package), I want to reset. + boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); + if (isPackagedManaged) { + Logger.d(TAG, "It seems that an addon I use (in package " + packageNameSchemePart + ") has been changed. I need to reload stuff."); + return true; + } else { + boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); + if (hasAddon) { + Logger.d(TAG, "It seems that an addon exists in an updated package " + packageNameSchemePart + ". I need to reload stuff."); + return true; + } + } + } else //removed + { + //so only if I manage this package, I want to reset + boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); + if (isPackagedManaged) { + Logger.d(TAG, "It seems that an addon I use (in package " + packageNameSchemePart + ") has been removed. I need to reload stuff."); + return true; + } + } + return false; + } + + protected boolean isPackageManaged(String packageNameSchemePart) { + for (AddOn addOn : mAddOns) { + if (addOn.getPackageName().equals(packageNameSchemePart)) { + return true; + } + } + + return false; + } + + protected boolean isPackageContainAnAddon(Context context, String packageNameSchemePart) { + PackageInfo newPackage; + try { + newPackage = context.getPackageManager().getPackageInfo(packageNameSchemePart, PackageManager.GET_RECEIVERS + PackageManager + .GET_META_DATA); + } catch (NameNotFoundException e) { + e.printStackTrace(); + return false; + } + if (newPackage.receivers != null) { + ActivityInfo[] receivers = newPackage.receivers; + for (ActivityInfo aReceiver : receivers) { + //issue 904 + if (aReceiver == null || aReceiver.applicationInfo == null || !aReceiver.enabled || !aReceiver.applicationInfo.enabled) + continue; + final XmlPullParser xml = aReceiver.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); + if (xml != null) { + return true; + } + } + } + + return false; + } + + protected boolean isEventRequiresViewReset(Intent eventIntent, Context context) { + return false; + } + + protected synchronized void clearAddOnList() { + mAddOns.clear(); + mAddOnsById.clear(); + } + + public synchronized E getAddOnById(String id, Context askContext) { + if (mAddOnsById.size() == 0) { + loadAddOns(askContext); + } + return mAddOnsById.get(id); + } + + public synchronized final List getAllAddOns(Context askContext) { + Logger.d(TAG, "getAllAddOns has %d add on for %s", mAddOns.size(), getClass().getName()); + if (mAddOns.size() == 0) { + loadAddOns(askContext); + } + Logger.d(TAG, "getAllAddOns will return %d add on for %s", mAddOns.size(), getClass().getName()); + return Collections.unmodifiableList(mAddOns); + } + + protected void loadAddOns(final Context askContext) { + clearAddOnList(); + + //ArrayList local = getAddOnsFromResId(askContext, askContext, mBuildInAddOnsResId); + //for (E addon : local) { + // Logger.d(TAG, "Local add-on %s loaded", addon.getId()); + //} + //mAddOns.addAll(local); + ArrayList external = getExternalAddOns(askContext); + for (E addon : external) { + Logger.d(TAG, "External add-on %s loaded", addon.getId()); + } + mAddOns.addAll(external); + Logger.d(TAG, "Have %d add on for %s", mAddOns.size(), getClass().getName()); + + buildOtherDataBasedOnNewAddOns(mAddOns); + + //sorting the keyboards according to the requested + //sort order (from minimum to maximum) + Collections.sort(mAddOns, new AddOnsComparator(askContext)); + Logger.d(TAG, "Have %d add on for %s (after sort)", mAddOns.size(), getClass().getName()); + } + + protected void buildOtherDataBasedOnNewAddOns(ArrayList newAddOns) { + for (E addOn : newAddOns) + mAddOnsById.put(addOn.getId(), addOn); + } + + private ArrayList getExternalAddOns(Context askContext) { + final ArrayList externalAddOns = new ArrayList<>(); + + if (!mReadExternalPacksToo)//this will disable external packs (API careful stage) + return externalAddOns; + + final List broadcastReceivers = + askContext.getPackageManager().queryBroadcastReceivers(new Intent(RECEIVER_INTERFACE), PackageManager.GET_META_DATA); + + + for (final ResolveInfo receiver : broadcastReceivers) { + if (receiver.activityInfo == null) { + Logger.e(TAG, "BroadcastReceiver has null ActivityInfo. Receiver's label is " + + receiver.loadLabel(askContext.getPackageManager())); + Logger.e(TAG, "Is the external keyboard a service instead of BroadcastReceiver?"); + // Skip to next receiver + continue; + } + + if (!receiver.activityInfo.enabled || !receiver.activityInfo.applicationInfo.enabled) continue; + + try { + final Context externalPackageContext = askContext.createPackageContext(receiver.activityInfo.packageName, Context.CONTEXT_IGNORE_SECURITY); + final ArrayList packageAddOns = getAddOnsFromActivityInfo(askContext, externalPackageContext, receiver.activityInfo); + + externalAddOns.addAll(packageAddOns); + } catch (final NameNotFoundException e) { + Logger.e(TAG, "Did not find package: " + receiver.activityInfo.packageName); + } + + } + + return externalAddOns; + } + + private ArrayList getAddOnsFromResId(Context askContext, Context context, int addOnsResId) { + final XmlPullParser xml = context.getResources().getXml(addOnsResId); + if (xml == null) + return new ArrayList<>(); + return parseAddOnsFromXml(askContext, context, xml); + } + + private ArrayList getAddOnsFromActivityInfo(Context askContext, Context context, ActivityInfo ai) { + final XmlPullParser xml = ai.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); + if (xml == null)//issue 718: maybe a bad package? + return new ArrayList<>(); + return parseAddOnsFromXml(askContext, context, xml); + } + + private ArrayList parseAddOnsFromXml(Context askContext, Context context, XmlPullParser xml) { + final ArrayList addOns = new ArrayList<>(); + try { + int event; + boolean inRoot = false; + while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = xml.getName(); + if (event == XmlPullParser.START_TAG) { + if (ROOT_NODE_TAG.equals(tag)) { + inRoot = true; + } else if (inRoot && ADDON_NODE_TAG.equals(tag)) { + final AttributeSet attrs = Xml.asAttributeSet(xml); + E addOn = createAddOnFromXmlAttributes(askContext, attrs, context); + if (addOn != null) { + addOns.add(addOn); + } + } + } else if (event == XmlPullParser.END_TAG) { + if (ROOT_NODE_TAG.equals(tag)) { + inRoot = false; + break; + } + } + } + } catch (final IOException e) { + Logger.e(TAG, "IO error:" + e); + e.printStackTrace(); + } catch (final XmlPullParserException e) { + Logger.e(TAG, "Parse error:" + e); + e.printStackTrace(); + } + + return addOns; + } + + private E createAddOnFromXmlAttributes(Context askContext, AttributeSet attrs, Context context) { + final String prefId = attrs.getAttributeValue(null, XML_PREF_ID_ATTRIBUTE); + final int nameId = attrs.getAttributeResourceValue(null, XML_NAME_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + final int descriptionInt = attrs.getAttributeResourceValue(null, XML_DESCRIPTION_ATTRIBUTE, AddOn.INVALID_RES_ID); + //NOTE, to be compatible we need this. because the most of descriptions are + //without @string/adb + String description; + if (descriptionInt != AddOn.INVALID_RES_ID) { + description = context.getResources().getString(descriptionInt); + } else { + description = attrs.getAttributeValue(null, XML_DESCRIPTION_ATTRIBUTE); + } + + final int sortIndex = attrs.getAttributeUnsignedIntValue(null, XML_SORT_INDEX_ATTRIBUTE, 1); + + // asserting + if ((prefId == null) || (nameId == AddOn.INVALID_RES_ID)) { + Logger.e(TAG, "External add-on does not include all mandatory details! Will not create add-on."); + return null; + } else { + Logger.d(TAG, "External addon details: prefId:" + prefId + " nameId:" + nameId); + return createConcreteAddOn(askContext, context, prefId, nameId, description, sortIndex, attrs); + } + } + + protected abstract E createConcreteAddOn(Context askContext, Context context, String prefId, int nameId, String description, int sortIndex, AttributeSet attrs); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java new file mode 100644 index 0000000..0eeba70 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java @@ -0,0 +1,64 @@ +package com.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.res.Resources; +import android.support.annotation.NonNull; +import android.util.SparseIntArray; + +import com.anysoftkeyboard.utils.Logger; + +import java.util.ArrayList; +import java.util.List; + +class Support { + private static final String TAG = Support.class.getName(); + + /** + * Creates a mapping between the local styleable and the remote. + * @param localStyleableArray the local styleable to map against + * @param localContext local APK's Context + * @param remoteContext remote package's Context + * @param attributeIdMap a mapping between the remote-id -> local-id + * @return Always returns the remote version of localStyleableArray + */ + public static int[] createBackwardCompatibleStyleable(@NonNull int[] localStyleableArray, @NonNull Context localContext, @NonNull Context remoteContext, @NonNull SparseIntArray attributeIdMap) { + if (localContext == null) throw new NullPointerException("askContext can not be null"); + if (remoteContext == null) throw new NullPointerException("context can not be null"); + + final String remotePackageName = remoteContext.getPackageName(); + if (localContext.getPackageName().equals(remotePackageName)) { + Logger.d(TAG, "This is a local context ("+remotePackageName+"), optimization will be done."); + //optimization + for(int attrId : localStyleableArray) { + attributeIdMap.put(attrId, attrId); + } + return localStyleableArray; + } + final Resources localRes = localContext.getResources(); + final Resources remoteRes = remoteContext.getResources(); + List styleableIdList = new ArrayList<>(localStyleableArray.length); + for(int attrId : localStyleableArray) { + final boolean isAndroidAttribute = localRes.getResourcePackageName(attrId).equals("android"); + final int remoteAttrId; + + if (isAndroidAttribute) { + //android attribute IDs are the same always. So, I can optimize. + remoteAttrId = attrId; + } else { + final String attributeName = localRes.getResourceEntryName(attrId); + remoteAttrId = remoteRes.getIdentifier(attributeName, "attr", remotePackageName); + Logger.d(TAG, "attr "+attributeName+", local id "+attrId+", remote id "+remoteAttrId); + } + if (remoteAttrId != 0) { + attributeIdMap.put(remoteAttrId, attrId); + styleableIdList.add(remoteAttrId); + } + } + final int[] remoteMappedStyleable = new int[styleableIdList.size()]; + for(int i=0; i { + private static final String TAG = "ASK_KF"; + + private static final String XML_LAYOUT_RES_ID_ATTRIBUTE = "layoutResId"; + private static final String XML_LANDSCAPE_LAYOUT_RES_ID_ATTRIBUTE = "landscapeResId"; + private static final String XML_ICON_RES_ID_ATTRIBUTE = "iconResId"; + private static final String XML_DICTIONARY_NAME_ATTRIBUTE = "defaultDictionaryLocale"; + private static final String XML_ADDITIONAL_IS_LETTER_EXCEPTIONS_ATTRIBUTE = "additionalIsLetterExceptions"; + private static final String XML_SENTENCE_SEPARATOR_CHARACTERS_ATTRIBUTE = "sentenceSeparators"; + private static final String DEFAULT_SENTENCE_SEPARATORS = ".,!?)]:;"; + private static final String XML_PHYSICAL_TRANSLATION_RES_ID_ATTRIBUTE = "physicalKeyboardMappingResId"; + private static final String XML_DEFAULT_ATTRIBUTE = "defaultEnabled"; + + public KeyboardFactory() { + super(TAG, "com.liskovsoft.leankey.langpack.KEYBOARD", "com.liskovsoft.leankey.langpack.keyboards", + "Keyboards", "Keyboard", + 0, true); + } + + public List getAllAvailableKeyboards(Context askContext) { + return getAllAddOns(askContext); + } + + public List getEnabledKeyboards(Context askContext) { + final List allAddOns = getAllAddOns(askContext); + Logger.i(TAG, "Creating enabled addons list. I have a total of " + allAddOns.size() + " addons"); + + //getting shared prefs to determine which to create. + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(askContext); + + final ArrayList enabledAddOns = new ArrayList<>(); + for (int addOnIndex = 0; addOnIndex < allAddOns.size(); addOnIndex++) { + final KeyboardAddOnAndBuilder addOn = allAddOns.get(addOnIndex); + + final boolean addOnEnabled = sharedPreferences.getBoolean(addOn.getId(), addOn.getKeyboardDefaultEnabled()); + + if (addOnEnabled) { + enabledAddOns.add(addOn); + } + } + + // Fix: issue 219 + // Check if there is any keyboards created if not, lets create a default english keyboard + if (enabledAddOns.size() == 0) { + final SharedPreferences.Editor editor = sharedPreferences.edit(); + final KeyboardAddOnAndBuilder addOn = allAddOns.get(0); + editor.putBoolean(addOn.getId(), true); + editor.commit(); + enabledAddOns.add(addOn); + } + + for (final KeyboardAddOnAndBuilder addOn : enabledAddOns) { + Logger.d(TAG, "Factory provided addon: %s", addOn.getId()); + } + + return enabledAddOns; + } + + @Override + protected KeyboardAddOnAndBuilder createConcreteAddOn(Context askContext, Context context, String prefId, int nameId, String description, int sortIndex, AttributeSet attrs) { + final int layoutResId = attrs.getAttributeResourceValue(null, XML_LAYOUT_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + final int landscapeLayoutResId = attrs.getAttributeResourceValue(null, XML_LANDSCAPE_LAYOUT_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + //final int iconResId = attrs.getAttributeResourceValue(null, XML_ICON_RES_ID_ATTRIBUTE, R.drawable.sym_keyboard_notification_icon); + final int iconResId = 0; + final String defaultDictionary = attrs.getAttributeValue(null, XML_DICTIONARY_NAME_ATTRIBUTE); + final String additionalIsLetterExceptions = attrs.getAttributeValue(null, XML_ADDITIONAL_IS_LETTER_EXCEPTIONS_ATTRIBUTE); + String sentenceSeparators = attrs.getAttributeValue(null, XML_SENTENCE_SEPARATOR_CHARACTERS_ATTRIBUTE); + if (TextUtils.isEmpty(sentenceSeparators)) sentenceSeparators = DEFAULT_SENTENCE_SEPARATORS; + final int physicalTranslationResId = attrs.getAttributeResourceValue(null, XML_PHYSICAL_TRANSLATION_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + // A keyboard is enabled by default if it is the first one (index==1) + final boolean keyboardDefault = attrs.getAttributeBooleanValue(null, XML_DEFAULT_ATTRIBUTE, sortIndex == 1); + + // asserting + if ((prefId == null) || (nameId == AddOn.INVALID_RES_ID) || (layoutResId == AddOn.INVALID_RES_ID)) { + Logger.e(TAG, "External Keyboard does not include all mandatory details! Will not create keyboard."); + return null; + } else { + Logger.d(TAG, + "External keyboard details: prefId:" + prefId + " nameId:" + + nameId + " resId:" + layoutResId + + " landscapeResId:" + landscapeLayoutResId + + " iconResId:" + iconResId + " defaultDictionary:" + + defaultDictionary); + return new KeyboardAddOnAndBuilder(askContext, context, + prefId, nameId, layoutResId, landscapeLayoutResId, + defaultDictionary, iconResId, physicalTranslationResId, + additionalIsLetterExceptions, sentenceSeparators, + description, sortIndex, keyboardDefault); + } + } + + public boolean hasMultipleAlphabets(Context askContext) { + return getEnabledKeyboards(askContext).size() > 1; + } + + public Keyboard createKeyboard(Context context) { + List keyboardBuilders = getAllAvailableKeyboards(context); + if (keyboardBuilders.size() == 0) + return new Keyboard(context, 0x7f04000c); // ru keyboard resource id + // remember, only one external keyboard supported + return keyboardBuilders.get(0).createKeyboard(); + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java new file mode 100644 index 0000000..24f5118 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java @@ -0,0 +1,6 @@ +package com.anysoftkeyboard.utils; + +public class BuildConfig { + public final static boolean TESTING_BUILD = true; + public final static boolean DEBUG = true; +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java new file mode 100644 index 0000000..f76fcf4 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java @@ -0,0 +1,48 @@ +package com.anysoftkeyboard.utils; + +import android.os.Build; +import android.util.Log; + +/** + * Logger messages to Android's LogCat. Should be used only in DEBUG builds. + */ +public class LogCatLogProvider implements LogProvider { + @Override + public void v(String TAG, String text) { + Log.v(TAG, text); + } + + @Override + public void d(String TAG, String text) { + Log.d(TAG, text); + } + + @Override + public void yell(String TAG, String text) { + Log.w(TAG+"-YELL", text); + } + + @Override + public void i(String TAG, String text) { + Log.i(TAG, text); + } + + @Override + public void w(String TAG, String text) { + Log.w(TAG, text); + } + + @Override + public void e(String TAG, String text) { + Log.e(TAG, text); + } + + @Override + public void wtf(String TAG, String text) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + Log.wtf(TAG, text); + } else { + Log.e(TAG+" WTF", text); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java new file mode 100644 index 0000000..32a4848 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.utils; + +public interface LogProvider { + + void v(String TAG, String text); + + void d(String TAG, String text); + + void yell(String TAG, String text); + + void i(String TAG, String text); + + void w(String TAG, String text); + + void e(String TAG, String text); + + void wtf(String TAG, String text); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java new file mode 100644 index 0000000..f3a2aa1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.utils; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Locale; + +public class Logger { + public static final String NEW_LINE = System.getProperty("line.separator"); + + private static final StringBuilder msFormatBuilder = new StringBuilder(1024); + private static final java.util.Formatter msFormatter = new java.util.Formatter(msFormatBuilder, Locale.US); + + private static final String[] msLogs = new String[BuildConfig.TESTING_BUILD ? 225 : 0]; + private static final String LVL_V = "V"; + private static final String LVL_D = "D"; + private static final String LVL_YELL = "YELL"; + private static final String LVL_I = "I"; + private static final String LVL_W = "W"; + private static final String LVL_E = "E"; + private static final String LVL_WTF = "WTF"; + private static int msLogIndex = 0; + @NonNull + private static LogProvider msLogger = new LogCatLogProvider(); + + private Logger() { + //no instances please. + } + + public static void setLogProvider(@NonNull LogProvider logProvider) { + msLogger = logProvider; + } + + private synchronized static void addLog(String level, String tag, String message) { + if (BuildConfig.TESTING_BUILD) { + msLogs[msLogIndex] = System.currentTimeMillis() + "-" + level + "-[" + tag + "] " + message; + msLogIndex = (msLogIndex + 1) % msLogs.length; + } + } + + private synchronized static void addLog(String level, String tag, String message, Throwable t) { + if (BuildConfig.TESTING_BUILD) { + addLog(level, tag, message); + addLog(level, tag, getStackTrace(t)); + } + } + + @NonNull + public synchronized static ArrayList getAllLogLinesList() { + ArrayList lines = new ArrayList<>(msLogs.length); + if (msLogs.length > 0) { + int index = msLogIndex; + do { + index--; + if (index == -1) index = msLogs.length - 1; + String logLine = msLogs[index]; + if (logLine == null) + break; + lines.add(msLogs[index]); + } + while (index != msLogIndex); + } + return lines; + } + + @NonNull + public synchronized static String getAllLogLines() { + if (BuildConfig.TESTING_BUILD) { + ArrayList lines = getAllLogLinesList(); + //now to build the string + StringBuilder sb = new StringBuilder("Log contains " + lines.size() + " lines:"); + while (lines.size() > 0) { + String line = lines.remove(lines.size() - 1); + sb.append(NEW_LINE); + sb.append(line); + } + return sb.toString(); + } else { + return "Not supported in RELEASE mode!"; + } + } + + public synchronized static void v(String TAG, String text, Object... args) { + if (BuildConfig.DEBUG) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.v(TAG, msg); + addLog(LVL_V, TAG, msg); + } + } + + public synchronized static void v(String TAG, String text, Throwable t) { + if (BuildConfig.DEBUG) { + msLogger.v(TAG, text + NEW_LINE + t); + addLog(LVL_V, TAG, text, t); + } + } + + public synchronized static void d(String TAG, String text) { + if (BuildConfig.TESTING_BUILD) { + msLogger.d(TAG, text); + addLog(LVL_D, TAG, text); + } + } + + public synchronized static void d(String TAG, String text, Object... args) { + if (BuildConfig.TESTING_BUILD) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.d(TAG, msg); + addLog(LVL_D, TAG, msg); + } + } + + public synchronized static void d(String TAG, String text, Throwable t) { + if (BuildConfig.TESTING_BUILD) { + msLogger.d(TAG, text + NEW_LINE + t); + addLog(LVL_D, TAG, text, t); + } + } + + public synchronized static void yell(String TAG, String text, Object... args) { + if (BuildConfig.TESTING_BUILD) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.yell(TAG, msg); + addLog(LVL_YELL, TAG, msg); + } + } + + public synchronized static void i(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.i(TAG, msg); + addLog(LVL_I, TAG, msg); + } + + public synchronized static void i(String TAG, String text, Throwable t) { + msLogger.i(TAG, text + NEW_LINE + t); + addLog(LVL_I, TAG, text, t); + } + + public synchronized static void w(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.w(TAG, msg); + addLog(LVL_W, TAG, msg); + } + + public synchronized static void w(String TAG, String text, Throwable t) { + msLogger.w(TAG, text + NEW_LINE + t); + addLog(LVL_W, TAG, text, t); + } + + public synchronized static void e(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.e(TAG, msg); + addLog(LVL_E, TAG, msg); + } + + //TODO: remove this method + public synchronized static void e(String TAG, String text, Throwable t) { + msLogger.e(TAG, text + NEW_LINE + t); + addLog(LVL_E, TAG, text, t); + } + + public synchronized static void w(String TAG, Throwable e, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.e(TAG, msg + NEW_LINE + e); + addLog(LVL_E, TAG, msg); + } + + public synchronized static void wtf(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + addLog(LVL_WTF, TAG, msg); + msLogger.wtf(TAG, msg); + } + + public synchronized static void wtf(String TAG, String text, Throwable t) { + addLog(LVL_WTF, TAG, text, t); + msLogger.wtf(TAG, text + NEW_LINE + t); + } + + public static String getStackTrace(Throwable ex) { + StackTraceElement[] stackTrace = ex.getStackTrace(); + StringBuilder sb = new StringBuilder(); + + for (StackTraceElement element : stackTrace) { + sb.append("at ");//this is required for easy Proguard decoding. + sb.append(element.toString()); + sb.append(NEW_LINE); + } + + if (ex.getCause() == null) + return sb.toString(); + else { + ex = ex.getCause(); + String cause = getStackTrace(ex); + sb.append("*** Cause: ").append(ex.getClass().getName()); + sb.append(NEW_LINE); + sb.append("** Message: ").append(ex.getMessage()); + sb.append(NEW_LINE); + sb.append("** Stack track: ").append(cause); + sb.append(NEW_LINE); + return sb.toString(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java new file mode 100644 index 0000000..da8b669 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java @@ -0,0 +1,39 @@ +package com.anysoftkeyboard.utils; + +/** + * Doesn't do anything. For release. + */ +public class NullLogProvider implements LogProvider { + @Override + public void v(String TAG, String text) { + } + + @Override + public void d(String TAG, String text) { + } + + @Override + public void yell(String TAG, String text) { + + } + + @Override + public void i(String TAG, String text) { + + } + + @Override + public void w(String TAG, String text) { + + } + + @Override + public void e(String TAG, String text) { + + } + + @Override + public void wtf(String TAG, String text) { + + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java new file mode 100644 index 0000000..850c7db --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.utils; + +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class XmlUtils { + + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + + public static int convertValueToList(CharSequence value, String[] options, int defaultValue) { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + + return defaultValue; + } + + public static boolean convertValueToBoolean(CharSequence value, boolean defaultValue) { + boolean result = false; + + if (null == value) + return defaultValue; + + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + + return result; + } + + public static int convertValueToInt(CharSequence charSeq, int defaultValue) { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } else if ('#' == nm.charAt(index)) { + index++; + base = 16; + } + + return Integer.parseInt(nm.substring(index), base) * sign; + } + + public static int convertValueToUnsignedInt(String value, int defaultValue) { + if (null == value) + return defaultValue; + + return parseUnsignedIntAttribute(value); + } + + public static int parseUnsignedIntAttribute(CharSequence charSeq) { + String value = charSeq.toString(); + + long bits; + int index = 0; + int len = value.length(); + int base = 10; + + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + + char c = value.charAt(index + 1); + + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + + return (int) Long.parseLong(value.substring(index), base); + } + + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ +// public static final void writeMapXml(Map val, OutputStream out) +// throws XmlPullParserException, java.io.IOException { +// XmlSerializer serializer = new FastXmlSerializer(); +// serializer.setOutput(out, "utf-8"); +// serializer.startDocument(null, true); +// serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); +// writeMapXml(val, null, serializer); +// serializer.endDocument(); +// } + + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static void writeListXml(List val, OutputStream out) + throws XmlPullParserException, java.io.IOException { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + writeValueXml(e.getValue(), (String) e.getKey(), out); + } + + out.endTag(null, "map"); + } + + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + + int N = val.size(); + int i = 0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + + out.endTag(null, "list"); + } + + public static void writeSetXml(Set val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "set"); + if (name != null) { + out.attribute(null, "name", name); + } + + for (Object v : val) { + writeValueXml(v, null, out); + } + + out.endTag(null, "set"); + } + + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * @see #writeMapXml + * @see #writeValueXml + */ + public static void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + StringBuilder sb = new StringBuilder(val.length * 2); + for (byte b : val) { + int h = b >> 4; + sb.append(h >= 10 ? ('a' + h - 10) : ('0' + h)); + h = b & 0xff; + sb.append(h >= 10 ? ('a' + h - 10) : ('0' + h)); + } + + out.text(sb.toString()); + + out.endTag(null, "byte-array"); + } + + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int aVal : val) { + out.startTag(null, "item"); + out.attribute(null, "value", Integer.toString(aVal)); + out.endTag(null, "item"); + } + + out.endTag(null, "int-array"); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + *

+ * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + public static void writeValueXml(Object v, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + String typeStr; + if (v == null) { + out.startTag(null, "null"); + if (name != null) { + out.attribute(null, "name", name); + } + out.endTag(null, "null"); + return; + } else if (v instanceof String) { + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else if (v instanceof Integer) { + typeStr = "int"; + } else if (v instanceof Long) { + typeStr = "long"; + } else if (v instanceof Float) { + typeStr = "float"; + } else if (v instanceof Double) { + typeStr = "double"; + } else if (v instanceof Boolean) { + typeStr = "boolean"; + } else if (v instanceof byte[]) { + writeByteArrayXml((byte[]) v, name, out); + return; + } else if (v instanceof int[]) { + writeIntArrayXml((int[]) v, name, out); + return; + } else if (v instanceof Map) { + writeMapXml((Map) v, name, out); + return; + } else if (v instanceof List) { + writeListXml((List) v, name, out); + return; + } else if (v instanceof Set) { + writeSetXml((Set) v, name, out); + return; + } else if (v instanceof CharSequence) { + // XXX This is to allow us to at least write something if + // we encounter styled text... but it means we will drop all + // of the styling information. :( + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else { + throw new RuntimeException("writeValueXml: unable to write value " + v); + } + + out.startTag(null, typeStr); + if (name != null) { + out.attribute(null, "name", name); + } + out.attribute(null, "value", v.toString()); + out.endTag(null, typeStr); + } + + /** + * Read a HashMap from an InputStream containing XML. The stream can + * previously have been written by writeMapXml(). + * + * @param in The InputStream from which to read. + * @return HashMap The resulting map. + * @see #readListXml + * @see #readValueXml + * @see #readThisMapXml + * #see #writeMapXml + */ + public static HashMap readMapXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashMap) readValueXml(parser, new String[1]); + } + + /** + * Read an ArrayList from an InputStream containing XML. The stream can + * previously have been written by writeListXml(). + * + * @param in The InputStream from which to read. + * @return ArrayList The resulting list. + * @see #readMapXml + * @see #readValueXml + * @see #readThisListXml + * @see #writeListXml + */ + public static ArrayList readListXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (ArrayList) readValueXml(parser, new String[1]); + } + + + /** + * Read a HashSet from an InputStream containing XML. The stream can + * previously have been written by writeSetXml(). + * + * @param in The InputStream from which to read. + * @return HashSet The resulting set. + * @throws XmlPullParserException + * @throws java.io.IOException + * @see #readValueXml + * @see #readThisSetXml + * @see #writeSetXml + */ + public static HashSet readSetXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashSet) readValueXml(parser, new String[1]); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned after the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * @return HashMap The newly generated map. + * @see #readMapXml + */ + public static HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + HashMap map = new HashMap<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + if (name[0] != null) { + map.put(name[0], val); + } else { + throw new XmlPullParserException( + "Map value without name attribute: " + parser.getName()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * @return HashMap The newly generated list. + * @see #readListXml + */ + public static ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + ArrayList list = new ArrayList<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + list.add(val); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * @return HashSet The newly generated set. + * @throws XmlPullParserException + * @throws java.io.IOException + * @see #readSetXml + */ + public static HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + HashSet set = new HashSet<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + set.add(val); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return set; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * @return Returns a newly generated int[]. + * @see #readListXml + */ + public static int[] readThisIntArrayXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + + int[] array = new int[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned at the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * @return Object The newly generated value object. + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException { + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + return readThisValueXml(parser, name); + } else if (eventType == XmlPullParser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Unexpected end of document"); + } + + private static Object readThisValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + + Object res; + + switch (tagName) { + case "null": + res = null; + break; + case "string": + String value = ""; + int eventType; + while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + return value; + } + throw new XmlPullParserException("Unexpected end tag in : " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + value += parser.getText(); + } else if (eventType == XmlPullParser.START_TAG) { + throw new XmlPullParserException("Unexpected start tag in : " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in "); + case "int": + res = Integer.parseInt(parser.getAttributeValue(null, "value")); + break; + case "long": + res = Long.valueOf(parser.getAttributeValue(null, "value")); + break; + case "float": + res = Float.valueOf(parser.getAttributeValue(null, "value")); + break; + case "double": + res = Double.valueOf(parser.getAttributeValue(null, "value")); + break; + case "boolean": + res = Boolean.valueOf(parser.getAttributeValue(null, "value")); + break; + case "int-array": + parser.next(); + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + return res; + case "map": + parser.next(); + res = readThisMapXml(parser, "map", name); + name[0] = valueName; + return res; + case "list": + parser.next(); + res = readThisListXml(parser, "list", name); + name[0] = valueName; + return res; + case "set": + parser.next(); + res = readThisSetXml(parser, "set", name); + name[0] = valueName; + return res; + default: + throw new XmlPullParserException( + "Unknown tag: " + tagName); + } + + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + return res; + } + throw new XmlPullParserException("Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + throw new XmlPullParserException("Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == XmlPullParser.START_TAG) { + throw new XmlPullParserException("Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException("Unexpected end of document in <" + tagName + ">"); + } + + public static void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + public static void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + ; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java new file mode 100644 index 0000000..3fb6f36 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java @@ -0,0 +1,254 @@ + +/* + * Copyright (c) 2013 Menny Even-Danan + * + * 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.anysoftkeyboard.utils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.Writer; +import java.util.Stack; + +/** + * Makes writing XML much much easier. + * + * @author Henri Yandell + * @author Menny Even Danan - just + * added some features on Henri's initial version + * @version 0.2 + */ +public class XmlWriter { + + private static final String INDENT_STRING = " "; + private final boolean thisIsWriterOwner;// is this instance the owner? + private final Writer writer; // underlying writer + private final int indentingOffset; + private final Stack stack; // of xml entity names + private final StringBuffer attrs; // current attribute string + private boolean empty; // is the current node empty + private boolean justWroteText; + private boolean closed; // is the current node closed... + + /** + * Create an XmlWriter on top of an existing java.io.Writer. + * + * @throws IOException + */ + public XmlWriter(Writer writer, boolean takeOwnership, int indentingOffset, boolean addXmlPrefix) + throws IOException { + thisIsWriterOwner = takeOwnership; + this.indentingOffset = indentingOffset; + this.writer = writer; + this.closed = true; + this.stack = new Stack(); + this.attrs = new StringBuffer(); + if (addXmlPrefix) + this.writer.write("\n"); + } + + public XmlWriter(File outputFile) throws IOException { + this(new FileWriter(outputFile), true, 0, true); + } + + /** + * Begin to output an entity. + * + * @param String name of entity. + */ + public XmlWriter writeEntity(String name) throws IOException { + closeOpeningTag(true); + this.closed = false; + for (int tabIndex = 0; tabIndex < stack.size() + indentingOffset; tabIndex++) + this.writer.write(INDENT_STRING); + this.writer.write("<"); + this.writer.write(name); + stack.add(name); + this.empty = true; + this.justWroteText = false; + return this; + } + + // close off the opening tag + private void closeOpeningTag(final boolean newLine) throws IOException { + if (!this.closed) { + writeAttributes(); + this.closed = true; + this.writer.write(">"); + if (newLine) + this.writer.write("\n"); + } + } + + // write out all current attributes + private void writeAttributes() throws IOException { + this.writer.write(this.attrs.toString()); + this.attrs.setLength(0); + this.empty = false; + } + + /** + * Write an attribute out for the current entity. Any xml characters in the + * value are escaped. Currently it does not actually throw the exception, + * but the api is set that way for future changes. + * + * @param String name of attribute. + * @param String value of attribute. + */ + public XmlWriter writeAttribute(String attr, String value) { + this.attrs.append(" "); + this.attrs.append(attr); + this.attrs.append("=\""); + this.attrs.append(escapeXml(value)); + this.attrs.append("\""); + return this; + } + + /** + * End the current entity. This will throw an exception if it is called when + * there is not a currently open entity. + * + * @throws IOException + */ + public XmlWriter endEntity() throws IOException { + if (this.stack.empty()) { + throw new InvalidObjectException("Called endEntity too many times. "); + } + String name = this.stack.pop(); + if (name != null) { + if (this.empty) { + writeAttributes(); + this.writer.write("/>\n"); + } else { + if (!this.justWroteText) { + for (int tabIndex = 0; tabIndex < stack.size() + indentingOffset; tabIndex++) + this.writer.write(INDENT_STRING); + } + this.writer.write("\n"); + } + this.empty = false; + this.closed = true; + this.justWroteText = false; + } + return this; + } + + /** + * Close this writer. It does not close the underlying writer, but does + * throw an exception if there are as yet unclosed tags. + * + * @throws IOException + */ + public void close() throws IOException { + if (!this.stack.empty()) { + throw new InvalidObjectException("Tags are not all closed. " + + "Possibly, " + this.stack.pop() + " is unclosed. "); + } + if (thisIsWriterOwner) { + this.writer.flush(); + this.writer.close(); + } + } + + /** + * Output body text. Any xml characters are escaped. + */ + public XmlWriter writeText(String text) throws IOException { + closeOpeningTag(false); + this.empty = false; + this.justWroteText = true; + this.writer.write(escapeXml(text)); + return this; + } + + // Static functions lifted from generationjava helper classes + // to make the jar smaller. + + // from XmlW + static public String escapeXml(String str) { + str = replaceString(str, "&", "&"); + str = replaceString(str, "<", "<"); + str = replaceString(str, ">", ">"); + str = replaceString(str, "\"", """); + str = replaceString(str, "'", "'"); + return str; + } + + // from StringW + static public String replaceString(String text, String repl, String with) { + return replaceString(text, repl, with, -1); + } + + /** + * Replace a string with another string inside a larger string, for the + * first n values of the search string. + * + * @param text String to do search and replace in + * @param repl String to search for + * @param with String to replace with + * @param n int values to replace + * @return String with n values replacEd + */ + static public String replaceString(String text, String repl, String with, int max) { + if (text == null) { + return null; + } + + StringBuffer buffer = new StringBuffer(text.length()); + int start = 0; + int end = 0; + while ((end = text.indexOf(repl, start)) != -1) { + buffer.append(text.substring(start, end)).append(with); + start = end + repl.length(); + + if (--max == 0) { + break; + } + } + buffer.append(text.substring(start)); + + return buffer.toString(); + } + // + // static public void test1() throws WritingException { + // Writer writer = new java.io.StringWriter(); + // XmlWriter xmlwriter = new XmlWriter(writer); + // xmlwriter.writeEntity("person").writeAttribute("name", + // "fred").writeAttribute("age", + // "12").writeEntity("phone").writeText("4254343").endEntity().writeEntity("bob").endEntity().endEntity(); + // xmlwriter.close(); + // System.err.println(writer.toString()); + // } + // static public void test2() throws WritingException { + // Writer writer = new java.io.StringWriter(); + // XmlWriter xmlwriter = new XmlWriter(writer); + // xmlwriter.writeEntity("person"); + // xmlwriter.writeAttribute("name", "fred"); + // xmlwriter.writeAttribute("age", "12"); + // xmlwriter.writeEntity("phone"); + // xmlwriter.writeText("4254343"); + // xmlwriter.endEntity(); + // xmlwriter.writeEntity("bob"); + // xmlwriter.endEntity(); + // xmlwriter.endEntity(); + // xmlwriter.close(); + // System.err.println(writer.toString()); + // } + +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java new file mode 100644 index 0000000..4af1ad7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java @@ -0,0 +1,16 @@ +package com.google.android.leanback.ime; + +import android.util.EventLog; + +public class EventLogTags { + public static final int TIME_LEANBACK_IME_INPUT = 270900; + public static final int TOTAL_LEANBACK_IME_BACKSPACE = 270902; + + public static void writeTimeLeanbackImeInput(long var0, long var2) { + EventLog.writeEvent(270900, new Object[]{var0, var2}); + } + + public static void writeTotalLeanbackImeBackspace(int var0) { + EventLog.writeEvent(270902, var0); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java new file mode 100644 index 0000000..f85ead0 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java @@ -0,0 +1,1302 @@ +package com.google.android.leanback.ime; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.animation.Animator.AnimatorListener; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.SpeechRecognizer; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.Transformation; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import com.google.android.leanback.ime.voice.RecognizerView; +import com.google.android.leanback.ime.voice.SpeechLevelSource; +import com.google.leanback.ime.LeanbackImeService; +import com.liskovsoft.keyboardaddons.KeyboardManager; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; +import java.util.Locale; + +public class LeanbackKeyboardContainer { + private static final boolean DEBUG = false; + public static final double DIRECTION_STEP_MULTIPLIER = 1.25D; + private static final String IME_PRIVATE_OPTIONS_ESCAPE_NORTH = "EscapeNorth=1"; + private static final String IME_PRIVATE_OPTIONS_VOICE_DISMISS = "VoiceDismiss=1"; + private static final long MOVEMENT_ANIMATION_DURATION = 150L; + private static final int MSG_START_INPUT_VIEW = 0; + protected static final float PHYSICAL_HEIGHT_CM = 5.0F; + protected static final float PHYSICAL_WIDTH_CM = 12.0F; + private static final String TAG = "LbKbContainer"; + public static final double TOUCH_MOVE_MIN_DISTANCE = 0.1D; + public static final int TOUCH_STATE_CLICK = 3; + public static final int TOUCH_STATE_NO_TOUCH = 0; + public static final int TOUCH_STATE_TOUCH_MOVE = 2; + public static final int TOUCH_STATE_TOUCH_SNAP = 1; + private static final boolean VOICE_SUPPORTED = true; + public static final Interpolator sMovementInterpolator = new DecelerateInterpolator(1.5F); + private Keyboard mAbcKeyboard; + private Keyboard mAbcKeyboardRU; + private Button mActionButtonView; + private final float mAlphaIn; + private final float mAlphaOut; + private boolean mAutoEnterSpaceEnabled; + private boolean mCapCharacters; + private boolean mCapSentences; + private boolean mCapWords; + private final int mClickAnimDur; + private LeanbackImeService mContext; + private LeanbackKeyboardContainer.KeyFocus mCurrKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private LeanbackKeyboardContainer.DismissListener mDismissListener; + private LeanbackKeyboardContainer.KeyFocus mDownKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private CharSequence mEnterKeyText; + private int mEnterKeyTextResId; + private boolean mEscapeNorthEnabled; + private Keyboard mInitialMainKeyboard; + private KeyboardManager mKeyboardManager; + private View mKeyboardsContainer; + private LeanbackKeyboardView mMainKeyboardView; + private int mMiniKbKeyIndex; + private Keyboard mNumKeyboard; + private float mOverestimate; + private PointF mPhysicalSelectPos = new PointF(2.0F, 0.5F); + private PointF mPhysicalTouchPos = new PointF(2.0F, 0.5F); + private LeanbackKeyboardView mPrevView; + private Intent mRecognizerIntent; + private Rect mRect = new Rect(); + private RelativeLayout mRootView; + private View mSelector; + private LeanbackKeyboardContainer.ScaleAnimation mSelectorAnimation; + private ValueAnimator mSelectorAnimator; + private SpeechLevelSource mSpeechLevelSource; + private SpeechRecognizer mSpeechRecognizer; + private LinearLayout mSuggestions; + private View mSuggestionsBg; + private HorizontalScrollView mSuggestionsContainer; + private boolean mSuggestionsEnabled; + private Keyboard mSymKeyboard; + private LeanbackKeyboardContainer.KeyFocus mTempKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private PointF mTempPoint = new PointF(); + private boolean mTouchDown = false; + private int mTouchState = 0; + private final int mVoiceAnimDur; + private final LeanbackKeyboardContainer.VoiceIntroAnimator mVoiceAnimator; + private RecognizerView mVoiceButtonView; + private boolean mVoiceEnabled; + private AnimatorListener mVoiceEnterListener = new AnimatorListener() { + public void onAnimationCancel(Animator var1) { + } + + public void onAnimationEnd(Animator var1) { + } + + public void onAnimationRepeat(Animator var1) { + } + + public void onAnimationStart(Animator var1) { + LeanbackKeyboardContainer.this.mSelector.setVisibility(View.INVISIBLE); + LeanbackKeyboardContainer.this.startRecognition(LeanbackKeyboardContainer.this.mContext); + } + }; + private AnimatorListener mVoiceExitListener = new AnimatorListener() { + public void onAnimationCancel(Animator var1) { + } + + public void onAnimationEnd(Animator var1) { + LeanbackKeyboardContainer.this.mSelector.setVisibility(View.VISIBLE); + } + + public void onAnimationRepeat(Animator var1) { + } + + public void onAnimationStart(Animator var1) { + LeanbackKeyboardContainer.this.mVoiceButtonView.showNotListening(); + LeanbackKeyboardContainer.this.mSpeechRecognizer.cancel(); + LeanbackKeyboardContainer.this.mSpeechRecognizer.setRecognitionListener((RecognitionListener) null); + LeanbackKeyboardContainer.this.mVoiceOn = false; + } + }; + private boolean mVoiceKeyDismissesEnabled; + private LeanbackKeyboardContainer.VoiceListener mVoiceListener; + private boolean mVoiceOn; + private Float mX; + private Float mY; + + public LeanbackKeyboardContainer(Context context) { + this.mContext = (LeanbackImeService) context; + final Resources res = this.mContext.getResources(); + this.mVoiceAnimDur = res.getInteger(R.integer.voice_anim_duration); + this.mAlphaIn = res.getFraction(R.fraction.alpha_in, 1, 1); + this.mAlphaOut = res.getFraction(R.fraction.alpha_out, 1, 1); + this.mVoiceAnimator = new LeanbackKeyboardContainer.VoiceIntroAnimator(this.mVoiceEnterListener, this.mVoiceExitListener); + this.initKeyboards(); + this.mRootView = (RelativeLayout) this.mContext.getLayoutInflater().inflate(R.layout.root_leanback, null); + this.mKeyboardsContainer = this.mRootView.findViewById(R.id.keyboard); + this.mSuggestionsBg = this.mRootView.findViewById(R.id.candidate_background); + this.mSuggestionsContainer = (HorizontalScrollView) this.mRootView.findViewById(R.id.suggestions_container); + this.mSuggestions = (LinearLayout) this.mSuggestionsContainer.findViewById(R.id.suggestions); + this.mMainKeyboardView = (LeanbackKeyboardView) this.mRootView.findViewById(R.id.main_keyboard); + this.mVoiceButtonView = (RecognizerView) this.mRootView.findViewById(R.id.voice); + this.mActionButtonView = (Button) this.mRootView.findViewById(R.id.enter); + this.mSelector = this.mRootView.findViewById(R.id.selector); + this.mSelectorAnimation = new LeanbackKeyboardContainer.ScaleAnimation((FrameLayout) this.mSelector); + this.mOverestimate = this.mContext.getResources().getFraction(R.fraction.focused_scale, 1, 1); + final float scale = context.getResources().getFraction(R.fraction.clicked_scale, 1, 1); + this.mClickAnimDur = context.getResources().getInteger(R.integer.clicked_anim_duration); + this.mSelectorAnimator = ValueAnimator.ofFloat(new float[]{1.0F, scale}); + this.mSelectorAnimator.setDuration((long) this.mClickAnimDur); + this.mSelectorAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float value = (Float) animation.getAnimatedValue(); + LeanbackKeyboardContainer.this.mSelector.setScaleX(value); + LeanbackKeyboardContainer.this.mSelector.setScaleY(value); + } + }); + this.mSpeechLevelSource = new SpeechLevelSource(); + this.mVoiceButtonView.setSpeechLevelSource(this.mSpeechLevelSource); + this.mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this.mContext); + this.mVoiceButtonView.setCallback(new RecognizerView.Callback() { + public void onCancelRecordingClicked() { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + + public void onStartRecordingClicked() { + LeanbackKeyboardContainer.this.startVoiceRecording(); + } + + public void onStopRecordingClicked() { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + }); + } + + private void configureFocus(LeanbackKeyboardContainer.KeyFocus focus, Rect rect, int index, int type) { + focus.type = type; + focus.index = index; + focus.rect.set(rect); + } + + private void configureFocus(LeanbackKeyboardContainer.KeyFocus focus, Rect rect, int index, Key key, int type) { + focus.type = type; + if (key != null) { + if (key.codes != null) { + focus.code = key.codes[0]; + } else { + focus.code = 0; + } + + focus.index = index; + focus.label = key.label; + focus.rect.left = key.x + rect.left; + focus.rect.top = key.y + rect.top; + focus.rect.right = focus.rect.left + key.width; + focus.rect.bottom = focus.rect.top + key.height; + } + } + + private void escapeNorth() { + this.mDismissListener.onDismiss(false); + } + + private PointF getAlignmentPosition(final float posXCm, final float posYCm, final PointF result) { + final float width = (float) (this.mRootView.getWidth() - this.mRootView.getPaddingRight() - this.mRootView.getPaddingLeft()); + final float height = (float) (this.mRootView.getHeight() - this.mRootView.getPaddingTop() - this.mRootView.getPaddingBottom()); + final float size = this.mContext.getResources().getDimension(R.dimen.selector_size); + result.x = posXCm / 12.0F * (width - size) + (float) this.mRootView.getPaddingLeft(); + result.y = posYCm / 5.0F * (height - size) + (float) this.mRootView.getPaddingTop(); + return result; + } + + private void getPhysicalPosition(final float x, final float y, final PointF result) { + float width = (float) (this.mSelector.getWidth() / 2); + float height = (float) (this.mSelector.getHeight() / 2); + float posXCm = (float) (this.mRootView.getWidth() - this.mRootView.getPaddingRight() - this.mRootView.getPaddingLeft()); + float posYCm = (float) (this.mRootView.getHeight() - this.mRootView.getPaddingTop() - this.mRootView.getPaddingBottom()); + float size = this.mContext.getResources().getDimension(R.dimen.selector_size); + result.x = (x - width - (float) this.mRootView.getPaddingLeft()) * 12.0F / (posXCm - size); + result.y = (y - height - (float) this.mRootView.getPaddingTop()) * 5.0F / (posYCm - size); + } + + private PointF getTouchSnapPosition() { + PointF var1 = new PointF(); + this.getPhysicalPosition((float) this.mCurrKeyInfo.rect.centerX(), (float) this.mCurrKeyInfo.rect.centerY(), var1); + return var1; + } + + private void initKeyboards() { + this.mAbcKeyboard = new Keyboard(this.mContext, R.xml.qwerty_us); + this.mSymKeyboard = new Keyboard(this.mContext, R.xml.sym_us); + this.updateAddonKeyboard(); + this.mNumKeyboard = new Keyboard(this.mContext, R.xml.number); + } + + private boolean isMatch(Locale var1, Locale[] var2) { + int var4 = var2.length; + + for (int var3 = 0; var3 < var4; ++var3) { + Locale var5 = var2[var3]; + if ((TextUtils.isEmpty(var5.getLanguage()) || TextUtils.equals(var1.getLanguage(), var5.getLanguage())) && (TextUtils.isEmpty + (var5.getCountry()) || TextUtils.equals(var1.getCountry(), var5.getCountry()))) { + return true; + } + } + + return false; + } + + private void moveFocusToIndex(int var1, int var2) { + Key var3 = this.mMainKeyboardView.getKey(var1); + this.configureFocus(this.mTempKeyInfo, this.mRect, var1, var3, var2); + this.setTouchState(0); + this.setKbFocus(this.mTempKeyInfo, true, true); + } + + private void offsetRect(Rect var1, View var2) { + var1.left = 0; + var1.top = 0; + var1.right = var2.getWidth(); + var1.bottom = var2.getHeight(); + this.mRootView.offsetDescendantRectToMyCoords(var2, var1); + } + + private void onToggleCapsLock() { + this.onShiftDoubleClick(this.isCapsLockOn()); + } + + private void setImeOptions(Resources var1, EditorInfo var2) { + if (this.mInitialMainKeyboard == null) { + this.mInitialMainKeyboard = this.mAbcKeyboard; + } + + this.mSuggestionsEnabled = false; + this.mAutoEnterSpaceEnabled = false; + this.mVoiceEnabled = false; + this.mEscapeNorthEnabled = false; + this.mVoiceKeyDismissesEnabled = false; + label67: + switch (LeanbackUtils.getInputTypeClass(var2)) { + case 1: + switch (LeanbackUtils.getInputTypeVariation(var2)) { + case 16: + case 32: + case 160: + case 208: + this.mSuggestionsEnabled = false; + this.mAutoEnterSpaceEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + break label67; + case 96: + case 128: + case 144: + case 224: + this.mSuggestionsEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + default: + break label67; + } + case 2: + case 3: + case 4: + this.mSuggestionsEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + } + + if (this.mSuggestionsEnabled) { + if ((var2.inputType & 524288) == 0) { + ; + } + + this.mSuggestionsEnabled = false; + } + + if (this.mAutoEnterSpaceEnabled) { + if (this.mSuggestionsEnabled && this.mAutoEnterSpaceEnabled) { + ; + } + + this.mAutoEnterSpaceEnabled = false; + } + + if ((var2.inputType & 16384) != 0) { + ; + } + + this.mCapSentences = false; + if ((var2.inputType & 8192) == 0 && LeanbackUtils.getInputTypeVariation(var2) == 96) { + ; + } + + this.mCapWords = false; + if ((var2.inputType & 4096) != 0) { + ; + } + + this.mCapCharacters = false; + if (var2.privateImeOptions != null) { + if (var2.privateImeOptions.contains("EscapeNorth=1")) { + this.mEscapeNorthEnabled = false; + } + + if (var2.privateImeOptions.contains("VoiceDismiss=1")) { + this.mVoiceKeyDismissesEnabled = false; + } + } + + this.mEnterKeyText = var2.actionLabel; + if (TextUtils.isEmpty(this.mEnterKeyText)) { + switch (LeanbackUtils.getImeAction(var2)) { + case 2: + this.mEnterKeyTextResId = R.string.label_go_key; + return; + case 3: + this.mEnterKeyTextResId = R.string.label_search_key; + return; + case 4: + this.mEnterKeyTextResId = R.string.label_send_key; + return; + case 5: + this.mEnterKeyTextResId = R.string.label_next_key; + return; + default: + this.mEnterKeyTextResId = R.string.label_done_key; + } + } + + } + + private void setKbFocus(final LeanbackKeyboardContainer.KeyFocus focus, final boolean forceFocusChange, final boolean animate) { + boolean clicked = true; + if (!focus.equals(this.mCurrKeyInfo) || forceFocusChange) { + LeanbackKeyboardView prevView = this.mPrevView; + this.mPrevView = null; + boolean overestimateWidth = false; + boolean overestimateHeight = false; + switch (focus.type) { + case KeyFocus.TYPE_MAIN: + if (focus.code != 32) { + overestimateWidth = true; + } else { + overestimateWidth = false; + } + + LeanbackKeyboardView mainView = this.mMainKeyboardView; + int index = focus.index; + if (this.mTouchState == 3) { + overestimateHeight = true; + } else { + overestimateHeight = false; + } + + mainView.setFocus(index, overestimateHeight, overestimateWidth); + this.mPrevView = this.mMainKeyboardView; + overestimateHeight = true; + break; + case KeyFocus.TYPE_VOICE: + this.mVoiceButtonView.setMicFocused(true); + this.dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_ACTION: + LeanbackUtils.sendAccessibilityEvent(this.mActionButtonView, true); + this.dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_SUGGESTION: + this.dismissMiniKeyboard(); + } + + if (prevView != null && prevView != this.mPrevView) { + if (this.mTouchState != 3) { + clicked = false; + } + + prevView.setFocus(-1, clicked); + } + + this.setSelectorToFocus(focus.rect, overestimateWidth, overestimateHeight, animate); + this.mCurrKeyInfo.set(focus); + } + } + + private void setShiftState(int var1) { + this.mMainKeyboardView.setShiftState(var1); + } + + private void setTouchStateInternal(int var1) { + this.mTouchState = var1; + } + + private void startRecognition(Context var1) { + this.mRecognizerIntent = new Intent("android.speech.action.RECOGNIZE_SPEECH"); + this.mRecognizerIntent.putExtra("android.speech.extra.LANGUAGE_MODEL", "free_form"); + this.mRecognizerIntent.putExtra("android.speech.extra.PARTIAL_RESULTS", true); + this.mSpeechRecognizer.setRecognitionListener(new RecognitionListener() { + float peakRmsLevel = 0.0F; + int rmsCounter = 0; + + public void onBeginningOfSpeech() { + LeanbackKeyboardContainer.this.mVoiceButtonView.showRecording(); + } + + public void onBufferReceived(byte[] var1) { + } + + public void onEndOfSpeech() { + LeanbackKeyboardContainer.this.mVoiceButtonView.showRecognizing(); + LeanbackKeyboardContainer.this.mVoiceOn = false; + } + + public void onError(int var1) { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + switch (var1) { + case 4: + Log.d("LbKbContainer", "recognizer error server error"); + return; + case 5: + Log.d("LbKbContainer", "recognizer error client error"); + return; + case 6: + Log.d("LbKbContainer", "recognizer error speech timeout"); + return; + case 7: + Log.d("LbKbContainer", "recognizer error no match"); + return; + default: + Log.d("LbKbContainer", "recognizer other error " + var1); + } + } + + public void onEvent(int var1, Bundle var2) { + } + + public void onPartialResults(Bundle var1) { + synchronized (this) { + } + } + + public void onReadyForSpeech(Bundle var1) { + LeanbackKeyboardContainer.this.mVoiceButtonView.showListening(); + } + + public void onResults(Bundle var1) { + ArrayList var2 = var1.getStringArrayList("results_recognition"); + if (var2 != null && LeanbackKeyboardContainer.this.mVoiceListener != null) { + LeanbackKeyboardContainer.this.mVoiceListener.onVoiceResult((String) var2.get(0)); + } + + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + + public void onRmsChanged(float param1) { + // $FF: Couldn't be decompiled + throw new IllegalStateException("method not implemented"); + } + }); + this.mSpeechRecognizer.startListening(this.mRecognizerIntent); + } + + public void alignSelector(float var1, float var2, boolean var3) { + var1 -= (float) (this.mSelector.getWidth() / 2); + var2 -= (float) (this.mSelector.getHeight() / 2); + if (!var3) { + this.mSelector.setX(var1); + this.mSelector.setY(var2); + } else { + this.mSelector.animate().x(var1).y(var2).setInterpolator(sMovementInterpolator).setDuration(150L).start(); + } + } + + public boolean areSuggestionsEnabled() { + return this.mSuggestionsEnabled; + } + + public void cancelVoiceRecording() { + this.mVoiceAnimator.startExitAnimation(); + } + + public void clearSuggestions() { + this.mSuggestions.removeAllViews(); + if (this.getCurrFocus().type == 3) { + this.resetFocusCursor(); + } + + } + + public boolean dismissMiniKeyboard() { + return this.mMainKeyboardView.dismissMiniKeyboard(); + } + + public boolean enableAutoEnterSpace() { + return this.mAutoEnterSpaceEnabled; + } + + public boolean getBestFocus(final Float x, final Float y, final LeanbackKeyboardContainer.KeyFocus focus) { + this.offsetRect(this.mRect, this.mActionButtonView); + int actionLeft = this.mRect.left; + this.offsetRect(this.mRect, this.mMainKeyboardView); + int keyboardTop = this.mRect.top; + Float newX = x; + if (x == null) { + newX = this.mX; + } + + Float newY = y; + if (y == null) { + newY = this.mY; + } + + int count = this.mSuggestions.getChildCount(); + if (newY < (float) keyboardTop && count > 0 && this.mSuggestionsEnabled) { + for (actionLeft = 0; actionLeft < count; ++actionLeft) { + View view = this.mSuggestions.getChildAt(actionLeft); + this.offsetRect(this.mRect, view); + if (newX < (float) this.mRect.right || actionLeft + 1 == count) { + view.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(view.findViewById(R.id.text), true); + this.configureFocus(focus, this.mRect, actionLeft, 3); + break; + } + } + + return true; + } else if (newY < (float) keyboardTop && this.mEscapeNorthEnabled) { + this.escapeNorth(); + return false; + } else if (newX > (float) actionLeft) { + this.offsetRect(this.mRect, this.mActionButtonView); + this.configureFocus(focus, this.mRect, 0, 2); + return true; + } else { + this.mX = newX; + this.mY = newY; + this.offsetRect(this.mRect, this.mMainKeyboardView); + final float left = (float) this.mRect.left; + final float top = (float) this.mRect.top; + actionLeft = this.mMainKeyboardView.getNearestIndex(Float.valueOf(newX - left), Float.valueOf(newY - top)); + Key key = this.mMainKeyboardView.getKey(actionLeft); + this.configureFocus(focus, this.mRect, actionLeft, key, 0); + return true; + } + } + + public LeanbackKeyboardContainer.KeyFocus getCurrFocus() { + return this.mCurrKeyInfo; + } + + public int getCurrKeyCode() { + int var1 = 0; + Key var2 = this.getKey(this.mCurrKeyInfo.type, this.mCurrKeyInfo.index); + if (var2 != null) { + var1 = var2.codes[0]; + } + + return var1; + } + + public Button getGoButton() { + return this.mActionButtonView; + } + + public Key getKey(int var1, int var2) { + return var1 == 0 ? this.mMainKeyboardView.getKey(var2) : null; + } + + public boolean getNextFocusInDirection(int direction, LeanbackKeyboardContainer.KeyFocus startFocus, LeanbackKeyboardContainer.KeyFocus nextFocus) { + switch (startFocus.type) { + case KeyFocus.TYPE_MAIN: + Key key = this.getKey(startFocus.type, startFocus.index); + float var5 = (float) startFocus.rect.height() / 2.0F; + float var4 = (float) startFocus.rect.centerX(); + float var6 = (float) startFocus.rect.centerY(); + if (startFocus.code == 32) { + var4 = this.mX; + } + + if ((direction & 1) != 0) { + if ((key.edgeFlags & 1) == 0) { + var4 = (float) startFocus.rect.left - var5; + } + } else if ((direction & 4) != 0) { + if ((key.edgeFlags & 2) != 0) { + this.offsetRect(this.mRect, this.mActionButtonView); + var4 = (float) this.mRect.centerX(); + } else { + var4 = (float) startFocus.rect.right + var5; + } + } + + if ((direction & 8) != 0) { + var5 = (float) ((double) var6 - (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } else { + var5 = var6; + if ((direction & 2) != 0) { + var5 = (float) ((double) var6 + (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } + } + + this.getPhysicalPosition(var4, var5, this.mTempPoint); + return this.getBestFocus(var4, var5, nextFocus); + case KeyFocus.TYPE_VOICE: + default: + break; + case KeyFocus.TYPE_ACTION: + this.offsetRect(this.mRect, this.mMainKeyboardView); + if ((direction & 1) != 0) { + return this.getBestFocus((float) this.mRect.right, null, nextFocus); + } + + if ((direction & 8) != 0) { + this.offsetRect(this.mRect, this.mSuggestions); + return this.getBestFocus((float) startFocus.rect.centerX(), (float) this.mRect.centerY(), nextFocus); + } + break; + case KeyFocus.TYPE_SUGGESTION: + if ((direction & 2) != 0) { + this.offsetRect(this.mRect, this.mMainKeyboardView); + return this.getBestFocus((float) startFocus.rect.centerX(), (float) this.mRect.top, nextFocus); + } + + if ((direction & 8) != 0) { + if (this.mEscapeNorthEnabled) { + this.escapeNorth(); + return true; + } + } else { + boolean var7; + if ((direction & 1) != 0) { + var7 = true; + } else { + var7 = false; + } + + boolean var12; + if ((direction & 4) != 0) { + var12 = true; + } else { + var12 = false; + } + + if (var7 || var12) { + this.offsetRect(this.mRect, this.mRootView); + MarginLayoutParams var11 = (MarginLayoutParams) this.mSuggestionsContainer.getLayoutParams(); + int var8 = this.mRect.left + var11.leftMargin; + int var9 = this.mRect.right - var11.rightMargin; + int var10 = startFocus.index; + byte var13; + if (var7) { + var13 = -1; + } else { + var13 = 1; + } + + direction = var10 + var13; + View var14 = this.mSuggestions.getChildAt(direction); + if (var14 != null) { + this.offsetRect(this.mRect, var14); + if (this.mRect.left < var8 && this.mRect.right > var9) { + this.mRect.left = var8; + this.mRect.right = var9; + } else if (this.mRect.left < var8) { + this.mRect.right = this.mRect.width() + var8; + this.mRect.left = var8; + } else if (this.mRect.right > var9) { + this.mRect.left = var9 - this.mRect.width(); + this.mRect.right = var9; + } + + var14.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(var14.findViewById(R.id.text), true); + this.configureFocus(nextFocus, this.mRect, direction, 3); + return true; + } + } + } + } + + return true; + } + + public CharSequence getSuggestionText(int var1) { + Object var3 = null; + CharSequence var2 = (CharSequence) var3; + if (var1 >= 0) { + var2 = (CharSequence) var3; + if (var1 < this.mSuggestions.getChildCount()) { + Button var4 = (Button) this.mSuggestions.getChildAt(var1).findViewById(R.id.text); + var2 = (CharSequence) var3; + if (var4 != null) { + var2 = var4.getText(); + } + } + } + + return var2; + } + + public int getTouchState() { + return this.mTouchState; + } + + public RelativeLayout getView() { + return this.mRootView; + } + + public boolean isCapsLockOn() { + return this.mMainKeyboardView.getShiftState() == 2; + } + + public boolean isCurrKeyShifted() { + return this.mMainKeyboardView.isShifted(); + } + + public boolean isMiniKeyboardOnScreen() { + return this.mMainKeyboardView.isMiniKeyboardOnScreen(); + } + + public boolean isVoiceEnabled() { + return this.mVoiceEnabled; + } + + public boolean isVoiceVisible() { + return this.mVoiceButtonView.getVisibility() == 0; + } + + public void onInitInputView() { + this.resetFocusCursor(); + this.mSelector.setVisibility(View.VISIBLE); + } + + public boolean onKeyLongPress() { + int var1 = this.mCurrKeyInfo.code; + if (var1 == -1) { + this.onToggleCapsLock(); + this.setTouchState(0); + return true; + } else if (var1 == 32) { + this.switchToNextKeyboard(); + this.setTouchState(0); + return true; + } else { + if (this.mCurrKeyInfo.type == 0) { + this.mMainKeyboardView.onKeyLongPress(); + if (this.mMainKeyboardView.isMiniKeyboardOnScreen()) { + this.mMiniKbKeyIndex = this.mCurrKeyInfo.index; + this.moveFocusToIndex(this.mMainKeyboardView.getBaseMiniKbIndex(), 0); + return true; + } + } + + return false; + } + } + + public void onModeChangeClick() { + this.dismissMiniKeyboard(); + if (this.mMainKeyboardView.getKeyboard().equals(this.mSymKeyboard)) { + this.mMainKeyboardView.setKeyboard(this.mInitialMainKeyboard); + } else { + this.mMainKeyboardView.setKeyboard(this.mSymKeyboard); + } + } + + public void onPeriodEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters && !this.mCapWords && !this.mCapSentences) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters || this.mCapWords || this.mCapSentences) { + this.setShiftState(1); + return; + } + + } + + public void onShiftClick() { + byte var1; + if (this.mMainKeyboardView.isShifted()) { + var1 = 0; + } else { + var1 = 1; + } + + this.setShiftState(var1); + } + + public void onShiftDoubleClick(boolean var1) { + byte var2; + if (var1) { + var2 = 0; + } else { + var2 = 2; + } + + this.setShiftState(var2); + } + + public void onSpaceEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters && !this.mCapWords) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters || this.mCapWords) { + this.setShiftState(1); + return; + } + + } + + public void onStartInput(EditorInfo var1) { + this.setImeOptions(this.mContext.getResources(), var1); + this.mVoiceOn = false; + } + + @TargetApi(17) + public void onStartInputView() { + this.clearSuggestions(); + LayoutParams params = (LayoutParams) this.mKeyboardsContainer.getLayoutParams(); + if (this.mSuggestionsEnabled) { + params.removeRule(10); + this.mSuggestionsContainer.setVisibility(View.VISIBLE); + this.mSuggestionsBg.setVisibility(View.VISIBLE); + } else { + params.addRule(10); + this.mSuggestionsContainer.setVisibility(View.GONE); + this.mSuggestionsBg.setVisibility(View.GONE); + } + + this.mKeyboardsContainer.setLayoutParams(params); + this.mMainKeyboardView.setKeyboard(this.mInitialMainKeyboard); + this.mVoiceButtonView.setMicEnabled(this.mVoiceEnabled); + this.resetVoice(); + this.dismissMiniKeyboard(); + if (!TextUtils.isEmpty(this.mEnterKeyText)) { + this.mActionButtonView.setText(this.mEnterKeyText); + this.mActionButtonView.setContentDescription(this.mEnterKeyText); + } else { + this.mActionButtonView.setText(this.mEnterKeyTextResId); + this.mActionButtonView.setContentDescription(this.mContext.getString(this.mEnterKeyTextResId)); + } + + if (this.mCapCharacters) { + this.setShiftState(2); + } else if (!this.mCapSentences && !this.mCapWords) { + this.setShiftState(0); + } else { + this.setShiftState(1); + } + } + + public void onTextEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters) { + this.setShiftState(2); + } + + if (this.dismissMiniKeyboard()) { + this.moveFocusToIndex(this.mMiniKbKeyIndex, 0); + } + + } + + public void onVoiceClick() { + if (this.mVoiceButtonView != null) { + this.mVoiceButtonView.onClick(); + } + + } + + public void resetFocusCursor() { + this.offsetRect(this.mRect, this.mMainKeyboardView); + this.mX = (float) ((double) this.mRect.left + (double) this.mRect.width() * 0.45D); + this.mY = (float) ((double) this.mRect.top + (double) this.mRect.height() * 0.375D); + this.getBestFocus(this.mX, this.mY, this.mTempKeyInfo); + this.setKbFocus(this.mTempKeyInfo, true, false); + this.setTouchStateInternal(0); + this.mSelectorAnimator.reverse(); + this.mSelectorAnimator.end(); + } + + public void resetVoice() { + this.mMainKeyboardView.setAlpha(this.mAlphaIn); + this.mActionButtonView.setAlpha(this.mAlphaIn); + this.mVoiceButtonView.setAlpha(this.mAlphaOut); + this.mMainKeyboardView.setVisibility(View.VISIBLE); + this.mActionButtonView.setVisibility(View.VISIBLE); + this.mVoiceButtonView.setVisibility(View.INVISIBLE); + } + + public void setDismissListener(LeanbackKeyboardContainer.DismissListener listener) { + this.mDismissListener = listener; + } + + public void setFocus(LeanbackKeyboardContainer.KeyFocus focus) { + this.setKbFocus(focus, false, true); + } + + public void setSelectorToFocus(Rect rect, boolean overestimateWidth, boolean overestimateHeight, boolean animate) { + if (this.mSelector.getWidth() != 0 && this.mSelector.getHeight() != 0 && rect.width() != 0 && rect.height() != 0) { + final float width = (float) rect.width(); + final float height = (float) rect.height(); + float heightOver = height; + if (overestimateHeight) { + heightOver = height * this.mOverestimate; + } + + float widthOver = width; + if (overestimateWidth) { + widthOver = width * this.mOverestimate; + } + + float deltaY = heightOver; + float deltaX = widthOver; + if ((double) (Math.max(widthOver, heightOver) / Math.min(widthOver, heightOver)) < 1.1D) { + deltaY = Math.max(widthOver, heightOver); + deltaX = deltaY; + } + + final float x = rect.exactCenterX() - deltaX / 2.0F; + final float y = rect.exactCenterY() - deltaY / 2.0F; + this.mSelectorAnimation.cancel(); + if (animate) { + this.mSelectorAnimation.reset(); + this.mSelectorAnimation.setAnimationBounds(x, y, deltaX, deltaY); + this.mSelector.startAnimation(this.mSelectorAnimation); + } else { + this.mSelectorAnimation.setValues(x, y, deltaX, deltaY); + } + } + } + + public void setTouchState(int var1) { + switch (var1) { + case 0: + if (this.mTouchState == 2 || this.mTouchState == 3) { + this.mSelectorAnimator.reverse(); + } + break; + case 1: + if (this.mTouchState == 3) { + this.mSelectorAnimator.reverse(); + } else if (this.mTouchState == 2) { + this.mSelectorAnimator.reverse(); + } + break; + case 2: + if (this.mTouchState == 0 || this.mTouchState == 1) { + this.mSelectorAnimator.start(); + } + break; + case 3: + if (this.mTouchState == 0 || this.mTouchState == 1) { + this.mSelectorAnimator.start(); + } + } + + this.setTouchStateInternal(var1); + this.setKbFocus(this.mCurrKeyInfo, true, true); + } + + public void setVoiceListener(LeanbackKeyboardContainer.VoiceListener var1) { + this.mVoiceListener = var1; + } + + public void startVoiceRecording() { + if (this.mVoiceEnabled) { + if (!this.mVoiceKeyDismissesEnabled) { + this.mVoiceAnimator.startEnterAnimation(); + return; + } + + this.mDismissListener.onDismiss(true); + } + + } + + public void switchToNextKeyboard() { + LeanbackKeyboardView var1 = this.mMainKeyboardView; + Keyboard var2 = this.mKeyboardManager.getNextKeyboard(); + this.mInitialMainKeyboard = var2; + var1.setKeyboard(var2); + } + + public void updateAddonKeyboard() { + KeyboardManager var1 = new KeyboardManager(this.mContext, this.mAbcKeyboard); + this.mKeyboardManager = var1; + this.mInitialMainKeyboard = var1.getNextKeyboard(); + } + + public void updateSuggestions(ArrayList var1) { + int var2 = this.mSuggestions.getChildCount(); + int var3 = var1.size(); + if (var3 < var2) { + this.mSuggestions.removeViews(var3, var2 - var3); + } else if (var3 > var2) { + while (var2 < var3) { + View var4 = this.mContext.getLayoutInflater().inflate(R.layout.candidate, (ViewGroup) null); + this.mSuggestions.addView(var4); + ++var2; + } + } + + for (var2 = 0; var2 < var3; ++var2) { + Button var5 = (Button) this.mSuggestions.getChildAt(var2).findViewById(R.id.text); + var5.setText((CharSequence) var1.get(var2)); + var5.setContentDescription((CharSequence) var1.get(var2)); + } + + if (this.getCurrFocus().type == 3) { + this.resetFocusCursor(); + } + + } + + public interface DismissListener { + void onDismiss(boolean var1); + } + + public static class KeyFocus { + public static final int TYPE_ACTION = 2; + public static final int TYPE_INVALID = -1; + public static final int TYPE_MAIN = 0; + public static final int TYPE_SUGGESTION = 3; + public static final int TYPE_VOICE = 1; + int code; + int index; + CharSequence label; + final Rect rect = new Rect(); + int type = -1; + + public boolean equals(Object var1) { + if (this != var1) { + if (var1 == null || this.getClass() != var1.getClass()) { + return false; + } + + LeanbackKeyboardContainer.KeyFocus var2 = (LeanbackKeyboardContainer.KeyFocus) var1; + if (this.code != var2.code) { + return false; + } + + if (this.index != var2.index) { + return false; + } + + if (this.type != var2.type) { + return false; + } + + label31: + { + if (this.label != null) { + if (this.label.equals(var2.label)) { + break label31; + } + } else if (var2.label == null) { + break label31; + } + + return false; + } + + if (!this.rect.equals(var2.rect)) { + return false; + } + } + + return true; + } + + public int hashCode() { + int var2 = this.rect.hashCode(); + int var3 = this.index; + int var4 = this.type; + int var5 = this.code; + int var1; + if (this.label != null) { + var1 = this.label.hashCode(); + } else { + var1 = 0; + } + + return (((var2 * 31 + var3) * 31 + var4) * 31 + var5) * 31 + var1; + } + + public void set(LeanbackKeyboardContainer.KeyFocus var1) { + this.index = var1.index; + this.type = var1.type; + this.code = var1.code; + this.label = var1.label; + this.rect.set(var1.rect); + } + + public String toString() { + StringBuilder var1 = new StringBuilder(); + var1.append("[type: ").append(this.type).append(", index: ").append(this.index).append(", code: ").append(this.code).append(", label: " + + "").append(this.label).append(", rect: ").append(this.rect).append("]"); + return var1.toString(); + } + } + + private class ScaleAnimation extends Animation { + private float mEndHeight; + private float mEndWidth; + private float mEndX; + private float mEndY; + private final android.view.ViewGroup.LayoutParams mParams; + private float mStartHeight; + private float mStartWidth; + private float mStartX; + private float mStartY; + private final View mView; + + public ScaleAnimation(FrameLayout var2) { + this.mView = var2; + this.mParams = var2.getLayoutParams(); + this.setDuration(150L); + this.setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator); + } + + protected void applyTransformation(float var1, Transformation var2) { + if (var1 == 0.0F) { + this.mStartX = this.mView.getX(); + this.mStartY = this.mView.getY(); + this.mStartWidth = (float) this.mParams.width; + this.mStartHeight = (float) this.mParams.height; + } else { + this.setValues((this.mEndX - this.mStartX) * var1 + this.mStartX, (this.mEndY - this.mStartY) * var1 + this.mStartY, (float) ((int) + ((this.mEndWidth - this.mStartWidth) * var1 + this.mStartWidth)), (float) ((int) ((this.mEndHeight - this.mStartHeight) * + var1 + this.mStartHeight))); + } + } + + public void setAnimationBounds(float var1, float var2, float var3, float var4) { + this.mEndX = var1; + this.mEndY = var2; + this.mEndWidth = var3; + this.mEndHeight = var4; + } + + public void setValues(float var1, float var2, float var3, float var4) { + this.mView.setX(var1); + this.mView.setY(var2); + this.mParams.width = (int) var3; + this.mParams.height = (int) var4; + this.mView.setLayoutParams(this.mParams); + this.mView.requestLayout(); + } + } + + private class VoiceIntroAnimator { + private AnimatorListener mEnterListener; + private AnimatorListener mExitListener; + private ValueAnimator mValueAnimator; + + public VoiceIntroAnimator(AnimatorListener var2, AnimatorListener var3) { + this.mEnterListener = var2; + this.mExitListener = var3; + this.mValueAnimator = ValueAnimator.ofFloat(new float[]{LeanbackKeyboardContainer.this.mAlphaOut, LeanbackKeyboardContainer.this.mAlphaIn}); + this.mValueAnimator.setDuration((long) LeanbackKeyboardContainer.this.mVoiceAnimDur); + this.mValueAnimator.setInterpolator(new AccelerateInterpolator()); + } + + private void start(final boolean var1) { + this.mValueAnimator.cancel(); + this.mValueAnimator.removeAllListeners(); + ValueAnimator var3 = this.mValueAnimator; + AnimatorListener var2; + if (var1) { + var2 = this.mEnterListener; + } else { + var2 = this.mExitListener; + } + + var3.addListener(var2); + this.mValueAnimator.removeAllUpdateListeners(); + this.mValueAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator var1x) { + float var2 = (Float) VoiceIntroAnimator.this.mValueAnimator.getAnimatedValue(); + float var4 = LeanbackKeyboardContainer.this.mAlphaIn + LeanbackKeyboardContainer.this.mAlphaOut - var2; + float var3; + if (var1) { + var3 = var4; + } else { + var3 = var2; + } + + if (var1) { + var4 = var2; + } + + LeanbackKeyboardContainer.this.mMainKeyboardView.setAlpha(var3); + LeanbackKeyboardContainer.this.mActionButtonView.setAlpha(var3); + LeanbackKeyboardContainer.this.mVoiceButtonView.setAlpha(var4); + if (var2 == LeanbackKeyboardContainer.this.mAlphaOut) { + if (!var1) { + LeanbackKeyboardContainer.this.mMainKeyboardView.setVisibility(View.VISIBLE); + LeanbackKeyboardContainer.this.mActionButtonView.setVisibility(View.VISIBLE); + return; + } + + LeanbackKeyboardContainer.this.mVoiceButtonView.setVisibility(View.VISIBLE); + } else if (var2 == LeanbackKeyboardContainer.this.mAlphaIn) { + if (var1) { + LeanbackKeyboardContainer.this.mMainKeyboardView.setVisibility(View.INVISIBLE); + LeanbackKeyboardContainer.this.mActionButtonView.setVisibility(View.INVISIBLE); + return; + } + + LeanbackKeyboardContainer.this.mVoiceButtonView.setVisibility(View.INVISIBLE); + return; + } + + } + }); + this.mValueAnimator.start(); + } + + void startEnterAnimation() { + if (!LeanbackKeyboardContainer.this.isVoiceVisible() && !this.mValueAnimator.isRunning()) { + this.start(true); + } + + } + + void startExitAnimation() { + if (LeanbackKeyboardContainer.this.isVoiceVisible() && !this.mValueAnimator.isRunning()) { + this.start(false); + } + + } + } + + public interface VoiceListener { + void onVoiceResult(String var1); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java new file mode 100644 index 0000000..a150608 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java @@ -0,0 +1,822 @@ +package com.google.android.leanback.ime; + +import android.graphics.PointF; +import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard.Key; +import android.os.Handler; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnHoverListener; +import android.view.View.OnLayoutChangeListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.RelativeLayout; +import com.google.android.pano.util.TouchNavSpaceTracker; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; + +public class LeanbackKeyboardController implements LeanbackKeyboardContainer.VoiceListener, LeanbackKeyboardContainer.DismissListener, OnTouchListener, OnHoverListener, Runnable { + public static final int CLICK_MOVEMENT_BLOCK_DURATION_MS = 500; + private static final boolean DEBUG = false; + private static final int KEY_CHANGE_HISTORY_SIZE = 10; + private static final long KEY_CHANGE_REVERT_TIME_MS = 100L; + private static final String TAG = "LbKbController"; + private boolean clickConsumed; + private long lastClickTime; + private LeanbackKeyboardContainer mContainer; + private InputMethodService mContext; + private LeanbackKeyboardController.DoubleClickDetector mDoubleClickDetector; + private LeanbackKeyboardContainer.KeyFocus mDownFocus; + private Handler mHandler; + private LeanbackKeyboardController.InputListener mInputListener; + ArrayList mKeyChangeHistory; + private LeanbackKeyboardContainer.KeyFocus mKeyDownKeyFocus; + private boolean mKeyDownReceived; + private boolean mLongPressHandled; + private int mMoveCount; + private OnLayoutChangeListener mOnLayoutChangeListener; + public float mResizeSquareDistance; + private TouchNavSpaceTracker mSpaceTracker; + private LeanbackKeyboardContainer.KeyFocus mTempFocus; + private PointF mTempPoint; + private LeanbackKeyboardController.TouchEventListener mTouchEventListener; + private long prevTime; + + public LeanbackKeyboardController(InputMethodService var1, LeanbackKeyboardController.InputListener var2) { + this(var1, var2, new TouchNavSpaceTracker(), new LeanbackKeyboardContainer(var1)); + } + + LeanbackKeyboardController(InputMethodService var1, LeanbackKeyboardController.InputListener var2, TouchNavSpaceTracker var3, LeanbackKeyboardContainer var4) { + this.mDoubleClickDetector = new LeanbackKeyboardController.DoubleClickDetector(); + this.mOnLayoutChangeListener = new OnLayoutChangeListener() { + public void onLayoutChange(View var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9) { + var2 = var4 - var2; + var3 = var5 - var3; + if (var2 > 0 && var3 > 0) { + if (LeanbackKeyboardController.this.mSpaceTracker != null) { + LeanbackKeyboardController.this.mSpaceTracker.setPixelSize((float)var2, (float)var3); + } + + if (var2 != var8 - var6 || var3 != var9 - var7) { + LeanbackKeyboardController.this.initInputView(); + } + } + + } + }; + this.mTouchEventListener = new LeanbackKeyboardController.TouchEventListener(); + this.mDownFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mTempFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mKeyChangeHistory = new ArrayList(11); + this.mTempPoint = new PointF(); + this.mKeyDownReceived = false; + this.mLongPressHandled = false; + this.mContext = var1; + this.mResizeSquareDistance = var1.getResources().getDimension(R.dimen.resize_move_distance); + this.mResizeSquareDistance *= this.mResizeSquareDistance; + this.mInputListener = var2; + this.setSpaceTracker(var3); + this.setKeyboardContainer(var4); + this.mContainer.setVoiceListener(this); + this.mContainer.setDismissListener(this); + } + + private boolean applyLETVFixesDown(int var1) { + switch(var1) { + case 82: + case 85: + case 89: + case 90: + return true; + default: + return false; + } + } + + private boolean applyLETVFixesUp(int var1) { + switch(var1) { + case 82: + this.mContainer.switchToNextKeyboard(); + break; + case 85: + this.fakeKeyIndex(0, 2); + break; + case 89: + this.fakeKeyCode(-5); + break; + case 90: + this.fakeKeyCode(32); + break; + default: + return false; + } + + return true; + } + + private void beginLongClickCountdown() { + this.clickConsumed = false; + Handler var2 = this.mHandler; + Handler var1 = var2; + if (var2 == null) { + var1 = new Handler(); + this.mHandler = var1; + } + + var1.removeCallbacks(this); + var1.postDelayed(this, (long)1000); + } + + private void clearKeyIfNecessary() { + ++this.mMoveCount; + if (this.mMoveCount >= 3) { + this.mMoveCount = 0; + this.mKeyDownKeyFocus = null; + } + + } + + private void commitKey() { + this.commitKey(this.mContainer.getCurrFocus()); + } + + private void commitKey(LeanbackKeyboardContainer.KeyFocus var1) { + if (this.mContainer != null && var1 != null) { + switch(var1.type) { + case 1: + this.mContainer.onVoiceClick(); + return; + case 2: + this.mInputListener.onEntry(5, 0, (CharSequence)null); + return; + case 3: + this.mInputListener.onEntry(2, 0, this.mContainer.getSuggestionText(var1.index)); + return; + default: + Key var2 = this.mContainer.getKey(var1.type, var1.index); + if (var2 != null) { + this.handleCommitKeyboardKey(var2.codes[0], var2.label); + return; + } + } + } + + } + + private void fakeClickDown() { + this.mContainer.setTouchState(3); + } + + private void fakeClickUp() { + LeanbackKeyboardContainer var1 = this.mContainer; + this.commitKey(var1.getCurrFocus()); + var1.setTouchState(0); + } + + private void fakeKeyCode(int var1) { + this.mContainer.getCurrFocus().code = var1; + this.handleCommitKeyboardKey(var1, (CharSequence)null); + } + + private void fakeKeyIndex(int var1, int var2) { + LeanbackKeyboardContainer.KeyFocus var3 = this.mContainer.getCurrFocus(); + var3.index = var1; + var3.type = var2; + this.commitKey(var3); + } + + private void fakeLongClickDown() { + LeanbackKeyboardContainer var1 = this.mContainer; + var1.onKeyLongPress(); + var1.setTouchState(3); + } + + private void fakeLongClickUp() { + this.mContainer.setTouchState(0); + } + + private PointF getBestSnapPosition(PointF var1, long var2) { + if (this.mKeyChangeHistory.size() <= 1) { + return var1; + } else { + int var4 = 0; + + PointF var5; + while(true) { + var5 = var1; + if (var4 >= this.mKeyChangeHistory.size() - 1) { + break; + } + + LeanbackKeyboardController.KeyChange var6 = (LeanbackKeyboardController.KeyChange)this.mKeyChangeHistory.get(var4); + if (var2 - ((LeanbackKeyboardController.KeyChange)this.mKeyChangeHistory.get(var4 + 1)).time < 100L) { + var5 = var6.position; + this.mKeyChangeHistory.clear(); + this.mKeyChangeHistory.add(new LeanbackKeyboardController.KeyChange(var2, var5)); + break; + } + + ++var4; + } + + return var5; + } + } + + private PointF getCurrentKeyPosition() { + if (this.mContainer != null) { + LeanbackKeyboardContainer.KeyFocus var1 = this.mContainer.getCurrFocus(); + return new PointF((float)var1.rect.centerX(), (float)var1.rect.centerY()); + } else { + return null; + } + } + + private PointF getRelativePosition(View var1, MotionEvent var2) { + int[] var5 = new int[2]; + var1.getLocationOnScreen(var5); + float var3 = var2.getRawX(); + float var4 = var2.getRawY(); + return new PointF(var3 - (float)var5[0], var4 - (float)var5[1]); + } + + private int getSimplifiedKey(int var1) { + int var2 = 23; + if (var1 != 23) { + byte var3 = 66; + var2 = var3; + if (var1 != 66) { + var2 = var3; + if (var1 != 160) { + var2 = var1; + if (var1 == 96) { + var2 = var3; + } + } + } + } + + var1 = var2; + if (var2 == 97) { + var1 = 4; + } + + return var1; + } + + private void handleCommitKeyboardKey(int var1, CharSequence var2) { + switch(var1) { + case -8: + this.mContainer.dismissMiniKeyboard(); + return; + case -7: + this.mContainer.startVoiceRecording(); + return; + case -6: + this.mContainer.onShiftDoubleClick(this.mContainer.isCapsLockOn()); + return; + case -5: + this.mInputListener.onEntry(1, 0, (CharSequence)null); + return; + case -4: + this.mInputListener.onEntry(4, 0, (CharSequence)null); + return; + case -3: + this.mInputListener.onEntry(3, 0, (CharSequence)null); + return; + case -2: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "mode change"); + } + + this.mContainer.onModeChangeClick(); + return; + case -1: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "shift"); + } + + this.mContainer.onShiftClick(); + return; + case 32: + this.mInputListener.onEntry(0, var1, " "); + this.mContainer.onSpaceEntry(); + return; + case 46: + this.mInputListener.onEntry(0, var1, var2); + this.mContainer.onPeriodEntry(); + return; + default: + this.mInputListener.onEntry(0, var1, var2); + this.mContainer.onTextEntry(); + if (this.mContainer.isMiniKeyboardOnScreen()) { + this.mContainer.dismissMiniKeyboard(); + } + + } + } + + private boolean handleKeyDownEvent(int var1, int var2) { + var1 = this.getSimplifiedKey(var1); + boolean var3; + boolean var4; + if (var1 == 4) { + this.mContainer.cancelVoiceRecording(); + var3 = false; + } else if (this.mContainer.isVoiceVisible()) { + if (var1 == 22 || var1 == 23 || var1 == 66) { + this.mContainer.cancelVoiceRecording(); + } + + var3 = true; + } else { + var4 = true; + var3 = var4; + switch(var1) { + case 19: + var3 = this.onDirectionalMove(8); + break; + case 20: + var3 = this.onDirectionalMove(2); + break; + case 21: + var3 = this.onDirectionalMove(1); + break; + case 22: + var3 = this.onDirectionalMove(4); + break; + case 23: + case 66: + if (var2 == 0) { + this.mMoveCount = 0; + this.mKeyDownKeyFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mKeyDownKeyFocus.set(this.mContainer.getCurrFocus()); + } else if (var2 == 1 && this.handleKeyLongPress(var1)) { + this.mKeyDownKeyFocus = null; + } + + var3 = var4; + if (this.isKeyHandledOnKeyDown(this.mContainer.getCurrKeyCode())) { + this.commitKey(); + var3 = var4; + } + break; + case 99: + this.handleCommitKeyboardKey(-5, (CharSequence)null); + var3 = var4; + break; + case 100: + this.handleCommitKeyboardKey(32, (CharSequence)null); + var3 = var4; + break; + case 102: + this.handleCommitKeyboardKey(-3, (CharSequence)null); + var3 = var4; + break; + case 103: + this.handleCommitKeyboardKey(-4, (CharSequence)null); + var3 = var4; + case 106: + case 107: + break; + default: + var3 = false; + } + } + + var4 = var3; + if (!var3) { + var4 = this.applyLETVFixesDown(var1); + } + + return var4; + } + + private boolean handleKeyLongPress(int var1) { + boolean var2; + if (this.isEnterKey(var1) && this.mContainer.onKeyLongPress()) { + var2 = true; + } else { + var2 = false; + } + + this.mLongPressHandled = var2; + if (this.mContainer.isMiniKeyboardOnScreen()) { + Log.d("LbKbController", "mini keyboard shown after long press"); + } + + return this.mLongPressHandled; + } + + private boolean handleKeyUpEvent(int var1, long var2) { + var1 = this.getSimplifiedKey(var1); + boolean var4; + boolean var5; + if (var1 == 4) { + var4 = false; + } else if (this.mContainer.isVoiceVisible()) { + var4 = true; + } else { + var5 = true; + var4 = var5; + switch(var1) { + case 19: + case 20: + case 21: + case 22: + this.clearKeyIfNecessary(); + var4 = var5; + break; + case 23: + case 66: + if (this.mContainer.getCurrKeyCode() == -1) { + this.mDoubleClickDetector.addEvent(var2); + var4 = var5; + } else { + var4 = var5; + if (!this.isKeyHandledOnKeyDown(this.mContainer.getCurrKeyCode())) { + this.commitKey(this.mKeyDownKeyFocus); + var4 = var5; + } + } + case 99: + case 100: + case 102: + case 103: + break; + case 106: + this.handleCommitKeyboardKey(-2, (CharSequence)null); + var4 = var5; + break; + case 107: + this.handleCommitKeyboardKey(-6, (CharSequence)null); + var4 = var5; + break; + default: + var4 = false; + } + } + + var5 = var4; + if (!var4) { + var5 = this.applyLETVFixesUp(var1); + } + + return var5; + } + + private void initInputView() { + this.mContainer.onInitInputView(); + this.updatePositionToCurrentFocus(); + } + + private boolean isCallAllowed(int var1) { + long var2 = System.currentTimeMillis(); + if (this.prevTime != 0L && var2 - this.prevTime <= (long)(var1 * 3)) { + if (var2 - this.prevTime > (long)var1) { + this.prevTime = 0L; + return true; + } + } else { + this.prevTime = var2; + } + + return false; + } + + private boolean isDoubleClick() { + long var1 = System.currentTimeMillis(); + long var3 = this.lastClickTime; + if (this.lastClickTime != 0L && var1 - var3 <= (long)300) { + return true; + } else { + this.lastClickTime = var1; + return false; + } + } + + private boolean isEnterKey(int var1) { + var1 = this.getSimplifiedKey(var1); + return var1 == 23 || var1 == 66; + } + + private boolean isKeyHandledOnKeyDown(int var1) { + return var1 == -5 || var1 == -3 || var1 == -4; + } + + private void moveSelectorToPoint(float var1, float var2) { + LeanbackKeyboardContainer var3 = this.mContainer; + LeanbackKeyboardContainer.KeyFocus var4 = this.mTempFocus; + var3.getBestFocus(new Float(var1), new Float(var2), var4); + this.mContainer.setFocus(this.mTempFocus); + var3 = this.mContainer; + Rect var5 = this.mTempFocus.rect; + var3.alignSelector((float)var5.centerX(), (float)var5.centerY(), false); + } + + private boolean onDirectionalMove(int var1) { + if (this.mContainer.getNextFocusInDirection(var1, this.mDownFocus, this.mTempFocus)) { + this.mContainer.setFocus(this.mTempFocus); + this.mDownFocus.set(this.mTempFocus); + this.clearKeyIfNecessary(); + } + + return true; + } + + private void performBestSnap(long var1) { + LeanbackKeyboardContainer.KeyFocus var3 = this.mContainer.getCurrFocus(); + this.mTempPoint.x = (float)var3.rect.centerX(); + this.mTempPoint.y = (float)var3.rect.centerY(); + PointF var4 = this.getBestSnapPosition(this.mTempPoint, var1); + this.mContainer.getBestFocus(var4.x, var4.y, this.mTempFocus); + this.mContainer.setFocus(this.mTempFocus); + this.updatePositionToCurrentFocus(); + } + + private void setKeyState(int var1, boolean var2) { + LeanbackKeyboardContainer var3 = this.mContainer; + LeanbackKeyboardContainer.KeyFocus var4 = var3.getCurrFocus(); + var4.index = var1; + var4.type = 0; + byte var5; + if (var2) { + var5 = 3; + } else { + var5 = 0; + } + + var3.setTouchState(var5); + } + + private void updatePositionToCurrentFocus() { + PointF var1 = this.getCurrentKeyPosition(); + if (var1 != null && this.mSpaceTracker != null) { + this.mSpaceTracker.setPixelPosition(var1.x, var1.y); + } + + } + + public boolean areSuggestionsEnabled() { + return this.mContainer != null ? this.mContainer.areSuggestionsEnabled() : false; + } + + public boolean enableAutoEnterSpace() { + return this.mContainer != null ? this.mContainer.enableAutoEnterSpace() : false; + } + + public View getView() { + if (this.mContainer != null) { + RelativeLayout var1 = this.mContainer.getView(); + var1.setClickable(true); + var1.setOnTouchListener(this); + var1.setOnHoverListener(this); + Button var2 = this.mContainer.getGoButton(); + var2.setOnTouchListener(this); + var2.setOnHoverListener(this); + var2.setTag("Go"); + return var1; + } else { + return null; + } + } + + public void onDismiss(boolean var1) { + if (var1) { + this.mInputListener.onEntry(8, 0, (CharSequence)null); + } else { + this.mInputListener.onEntry(7, 0, (CharSequence)null); + } + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + return this.mSpaceTracker != null && this.mContext != null && this.mContext.isInputViewShown() && this.mSpaceTracker.onGenericMotionEvent(var1); + } + + public boolean onHover(View var1, MotionEvent var2) { + boolean var4 = this.isCallAllowed(300); + boolean var3 = var4; + if (var4) { + if (var2.getAction() == 7) { + PointF var5 = this.getRelativePosition(this.mContainer.getView(), var2); + this.moveSelectorToPoint(var5.x, var5.y); + } + + var3 = true; + } + + return var3; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + this.mDownFocus.set(this.mContainer.getCurrFocus()); + if (this.mSpaceTracker != null && this.mSpaceTracker.onKeyDown(var1, var2)) { + return true; + } else { + if (this.isEnterKey(var1)) { + this.mKeyDownReceived = true; + if (var2.getRepeatCount() == 0) { + this.mContainer.setTouchState(3); + } + } + + return this.handleKeyDownEvent(var1, var2.getRepeatCount()); + } + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (this.mSpaceTracker != null && this.mSpaceTracker.onKeyUp(var1, var2)) { + return true; + } else { + if (this.isEnterKey(var1)) { + if (!this.mKeyDownReceived || this.mLongPressHandled) { + this.mLongPressHandled = false; + return true; + } + + this.mKeyDownReceived = false; + if (this.mContainer.getTouchState() == 3) { + this.mContainer.setTouchState(1); + } + } + + return this.handleKeyUpEvent(var1, var2.getEventTime()); + } + } + + public void onStartInput(EditorInfo var1) { + if (this.mContainer != null) { + this.mContainer.onStartInput(var1); + this.initInputView(); + } + + } + + public void onStartInputView() { + this.mKeyDownReceived = false; + if (this.mContainer != null) { + this.mContainer.onStartInputView(); + } + + this.mDoubleClickDetector.reset(); + } + + public boolean onTouch(View var1, MotionEvent var2) { + Object var3 = var1.getTag(); + if (var3 != null && "Go".equals(var3)) { + this.fakeKeyIndex(0, 2); + } else { + switch(var2.getAction()) { + case 0: + this.moveSelectorToPoint(var2.getX(), var2.getY()); + this.fakeClickDown(); + this.beginLongClickCountdown(); + break; + case 1: + if (!this.clickConsumed) { + this.clickConsumed = true; + if (this.isDoubleClick()) { + this.mContainer.onKeyLongPress(); + break; + } + + this.fakeClickUp(); + } + + this.fakeLongClickUp(); + break; + default: + return false; + } + } + + return true; + } + + public void onVoiceResult(String var1) { + this.mInputListener.onEntry(6, 0, var1); + } + + public void run() { + if (!this.clickConsumed) { + this.clickConsumed = true; + this.fakeLongClickDown(); + } + } + + public void setKeyboardContainer(LeanbackKeyboardContainer var1) { + this.mContainer = var1; + var1.getView().addOnLayoutChangeListener(this.mOnLayoutChangeListener); + } + + public void setSpaceTracker(TouchNavSpaceTracker var1) { + this.mSpaceTracker = var1; + var1.setLPFEnabled(true); + var1.setKeyEventListener(this.mTouchEventListener); + } + + public void updateAddonKeyboard() { + this.mContainer.updateAddonKeyboard(); + } + + public void updateSuggestions(ArrayList var1) { + if (this.mContainer != null) { + this.mContainer.updateSuggestions(var1); + } + + } + + private class DoubleClickDetector { + final long DOUBLE_CLICK_TIMEOUT_MS; + boolean mFirstClickShiftLocked; + long mFirstClickTime; + + private DoubleClickDetector() { + this.DOUBLE_CLICK_TIMEOUT_MS = 200L; + this.mFirstClickTime = 0L; + } + + public void addEvent(long var1) { + if (var1 - this.mFirstClickTime > 200L) { + this.mFirstClickTime = var1; + this.mFirstClickShiftLocked = LeanbackKeyboardController.this.mContainer.isCapsLockOn(); + LeanbackKeyboardController.this.commitKey(); + } else { + LeanbackKeyboardController.this.mContainer.onShiftDoubleClick(this.mFirstClickShiftLocked); + this.reset(); + } + } + + public void reset() { + this.mFirstClickTime = 0L; + } + } + + public interface InputListener { + int ENTRY_TYPE_ACTION = 5; + int ENTRY_TYPE_BACKSPACE = 1; + int ENTRY_TYPE_DISMISS = 7; + int ENTRY_TYPE_LEFT = 3; + int ENTRY_TYPE_RIGHT = 4; + int ENTRY_TYPE_STRING = 0; + int ENTRY_TYPE_SUGGESTION = 2; + int ENTRY_TYPE_VOICE = 6; + int ENTRY_TYPE_VOICE_DISMISS = 8; + + void onEntry(int var1, int var2, CharSequence var3); + } + + private static final class KeyChange { + public PointF position; + public long time; + + public KeyChange(long var1, PointF var3) { + this.time = var1; + this.position = var3; + } + } + + private class TouchEventListener implements TouchNavSpaceTracker.KeyEventListener { + private TouchEventListener() { + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + if (LeanbackKeyboardController.this.isEnterKey(var1)) { + LeanbackKeyboardController.this.mKeyDownReceived = true; + if (var2.getRepeatCount() == 0) { + LeanbackKeyboardController.this.mContainer.setTouchState(3); + LeanbackKeyboardController.this.mSpaceTracker.blockMovementUntil(var2.getEventTime() + 500L); + LeanbackKeyboardController.this.performBestSnap(var2.getEventTime()); + } + } + + return LeanbackKeyboardController.this.handleKeyDownEvent(var1, var2.getRepeatCount()); + } + + public boolean onKeyLongPress(int var1, KeyEvent var2) { + return LeanbackKeyboardController.this.handleKeyLongPress(var1); + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (LeanbackKeyboardController.this.isEnterKey(var1)) { + if (!LeanbackKeyboardController.this.mKeyDownReceived || LeanbackKeyboardController.this.mLongPressHandled) { + LeanbackKeyboardController.this.mLongPressHandled = false; + return true; + } + + LeanbackKeyboardController.this.mKeyDownReceived = false; + if (LeanbackKeyboardController.this.mContainer.getTouchState() == 3) { + LeanbackKeyboardController.this.mContainer.setTouchState(1); + LeanbackKeyboardController.this.mSpaceTracker.unblockMovement(); + } + } + + return LeanbackKeyboardController.this.handleKeyUpEvent(var1, var2.getEventTime()); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java new file mode 100644 index 0000000..d87cf46 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java @@ -0,0 +1,508 @@ +package com.google.android.leanback.ime; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.Bitmap.Config; +import android.graphics.Paint.Align; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.Iterator; +import java.util.List; + +public class LeanbackKeyboardView extends FrameLayout { + public static final int ASCII_PERIOD = 46; + public static final int ASCII_SPACE = 32; + private static final boolean DEBUG = false; + public static final int KEYCODE_CAPS_LOCK = -6; + public static final int KEYCODE_DELETE = -5; + public static final int KEYCODE_DISMISS_MINI_KEYBOARD = -8; + public static final int KEYCODE_LEFT = -3; + public static final int KEYCODE_RIGHT = -4; + public static final int KEYCODE_SHIFT = -1; + public static final int KEYCODE_SYM_TOGGLE = -2; + public static final int KEYCODE_VOICE = -7; + private static final int NOT_A_KEY = -1; + public static final int SHIFT_LOCKED = 2; + public static final int SHIFT_OFF = 0; + public static final int SHIFT_ON = 1; + private static final String TAG = "LbKbView"; + private int mBaseMiniKbIndex = -1; + private final int mClickAnimDur; + private final float mClickedScale; + private int mColCount; + private View mCurrentFocusView; + private boolean mFocusClicked; + private int mFocusIndex; + private final float mFocusedScale; + private final int mInactiveMiniKbAlpha; + private ImageView[] mKeyImageViews; + private int mKeyTextColor; + private int mKeyTextSize; + private Keyboard mKeyboard; + private LeanbackKeyboardView.KeyHolder[] mKeys; + private boolean mMiniKeyboardOnScreen; + private int mModeChangeTextSize; + private Rect mPadding; + private Paint mPaint; + private int mRowCount; + private int mShiftState; + private final int mUnfocusStartDelay; + + public LeanbackKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + Resources res = context.getResources(); + TypedArray styledAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LeanbackKeyboardView, 0, 0); + this.mRowCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_rowCount, -1); + this.mColCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_columnCount, -1); + this.mKeyTextSize = (int) res.getDimension(R.dimen.key_font_size); + this.mPaint = new Paint(); + this.mPaint.setAntiAlias(true); + this.mPaint.setTextSize((float) this.mKeyTextSize); + this.mPaint.setTextAlign(Align.CENTER); + this.mPaint.setAlpha(255); + this.mPadding = new Rect(0, 0, 0, 0); + this.mModeChangeTextSize = (int) res.getDimension(R.dimen.function_key_mode_change_font_size); + this.mKeyTextColor = res.getColor(R.color.key_text_default); + this.mFocusIndex = -1; + this.mShiftState = 0; + this.mFocusedScale = res.getFraction(R.fraction.focused_scale, 1, 1); + this.mClickedScale = res.getFraction(R.fraction.clicked_scale, 1, 1); + this.mClickAnimDur = res.getInteger(R.integer.clicked_anim_duration); + this.mUnfocusStartDelay = res.getInteger(R.integer.unfocused_anim_delay); + this.mInactiveMiniKbAlpha = res.getInteger(R.integer.inactive_mini_kb_alpha); + } + + private CharSequence adjustCase(LeanbackKeyboardView.KeyHolder keyHolder) { + CharSequence label = keyHolder.key.label; + CharSequence result = label; + if (label != null && label.length() < 3) { + boolean flag; + if (keyHolder.isInMiniKb && keyHolder.isInvertible) { + flag = true; + } else { + flag = false; + } + + if (this.mKeyboard.isShifted() ^ flag) { + result = label.toString().toUpperCase(); + } else { + result = label.toString().toLowerCase(); + } + + keyHolder.key.label = result; + } + + return result; + } + + @TargetApi(16) + private ImageView createKeyImageView(int keyIndex) { + Rect var8 = this.mPadding; + int var2 = this.getPaddingLeft(); + int var3 = this.getPaddingTop(); + LeanbackKeyboardView.KeyHolder var6 = this.mKeys[keyIndex]; + Key var7 = var6.key; + this.adjustCase(var6); + String var5; + if (var7.label == null) { + var5 = null; + } else { + var5 = var7.label.toString(); + } + + if (Log.isLoggable("LbKbView", Log.DEBUG)) { + Log.d("LbKbView", "LABEL: " + var7.label + "->" + var5); + } + + Bitmap var9 = Bitmap.createBitmap(var7.width, var7.height, Config.ARGB_8888); + Canvas var10 = new Canvas(var9); + Paint var11 = this.mPaint; + var11.setColor(this.mKeyTextColor); + var10.drawARGB(0, 0, 0, 0); + if (var7.icon != null) { + if (var7.codes[0] == -1) { + switch (this.mShiftState) { + case 0: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_off); + break; + case 1: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_on); + break; + case 2: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_lock_on); + } + } + + keyIndex = (var7.width - var8.left - var8.right - var7.icon.getIntrinsicWidth()) / 2 + var8.left; + int var4 = (var7.height - var8.top - var8.bottom - var7.icon.getIntrinsicHeight()) / 2 + var8.top; + var10.translate((float) keyIndex, (float) var4); + var7.icon.setBounds(0, 0, var7.icon.getIntrinsicWidth(), var7.icon.getIntrinsicHeight()); + var7.icon.draw(var10); + var10.translate((float) (-keyIndex), (float) (-var4)); + } else if (var5 != null) { + if (var5.length() > 1) { + var11.setTextSize((float) this.mModeChangeTextSize); + var11.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); + } else { + var11.setTextSize((float) this.mKeyTextSize); + var11.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + } + + var10.drawText(var5, (float) ((var7.width - var8.left - var8.right) / 2 + var8.left), (float) ((var7.height - var8.top - var8.bottom) / + 2) + (var11.getTextSize() - var11.descent()) / 2.0F + (float) var8.top, var11); + var11.setShadowLayer(0.0F, 0.0F, 0.0F, 0); + } + + ImageView var12 = new ImageView(this.getContext()); + var12.setImageBitmap(var9); + var12.setContentDescription(var5); + this.addView(var12, new LayoutParams(-2, -2)); + var12.setX((float) (var7.x + var2)); + var12.setY((float) (var7.y + var3)); + if (this.mMiniKeyboardOnScreen && !var6.isInMiniKb) { + keyIndex = this.mInactiveMiniKbAlpha; + } else { + keyIndex = 255; + } + + var12.setImageAlpha(keyIndex); + var12.setVisibility(View.VISIBLE); + return var12; + } + + private void createKeyImageViews(LeanbackKeyboardView.KeyHolder[] keys) { + int var3 = keys.length; + int var2; + if (this.mKeyImageViews != null) { + ImageView[] var5 = this.mKeyImageViews; + int var4 = var5.length; + + for (var2 = 0; var2 < var4; ++var2) { + this.removeView(var5[var2]); + } + + this.mKeyImageViews = null; + } + + for (var2 = 0; var2 < var3; ++var2) { + if (this.mKeyImageViews == null) { + this.mKeyImageViews = new ImageView[var3]; + } else if (this.mKeyImageViews[var2] != null) { + this.removeView(this.mKeyImageViews[var2]); + } + + this.mKeyImageViews[var2] = this.createKeyImageView(var2); + } + + } + + private void removeMessages() { + } + + private void setKeys(List keys) { + this.mKeys = new LeanbackKeyboardView.KeyHolder[keys.size()]; + Iterator var4 = keys.iterator(); + + for (int var2 = 0; var2 < this.mKeys.length && var4.hasNext(); ++var2) { + Key var3 = (Key) var4.next(); + this.mKeys[var2] = new LeanbackKeyboardView.KeyHolder(var3); + } + + } + + public boolean dismissMiniKeyboard() { + boolean var1 = false; + if (this.mMiniKeyboardOnScreen) { + this.mMiniKeyboardOnScreen = false; + this.setKeys(this.mKeyboard.getKeys()); + this.invalidateAllKeys(); + var1 = true; + } + + return var1; + } + + public int getBaseMiniKbIndex() { + return this.mBaseMiniKbIndex; + } + + public int getColCount() { + return this.mColCount; + } + + public Key getFocusedKey() { + return this.mFocusIndex == -1 ? null : this.mKeys[this.mFocusIndex].key; + } + + public Key getKey(int index) { + return this.mKeys != null && this.mKeys.length != 0 && index >= 0 && index <= this.mKeys.length ? this.mKeys[index].key : null; + } + + public Keyboard getKeyboard() { + return this.mKeyboard; + } + + public int getNearestIndex(float x, float y) { + int var7; + if (this.mKeys != null && this.mKeys.length != 0) { + float var3 = (float) this.getPaddingLeft(); + float var4 = (float) this.getPaddingTop(); + float var5 = (float) (this.getMeasuredHeight() - this.getPaddingTop() - this.getPaddingBottom()); + float var6 = (float) (this.getMeasuredWidth() - this.getPaddingLeft() - this.getPaddingRight()); + int var9 = this.getRowCount(); + int var10 = this.getColCount(); + int var8 = (int) ((y - var4) / var5 * (float) var9); + if (var8 < 0) { + var7 = 0; + } else { + var7 = var8; + if (var8 >= var9) { + var7 = var9 - 1; + } + } + + var9 = (int) ((x - var3) / var6 * (float) var10); + if (var9 < 0) { + var8 = 0; + } else { + var8 = var9; + if (var9 >= var10) { + var8 = var10 - 1; + } + } + + var8 += this.mColCount * var7; + var7 = var8; + if (var8 > 46) { + var7 = var8; + if (var8 < 53) { + var7 = 46; + } + } + + var8 = var7; + if (var7 >= 53) { + var8 = var7 - 6; + } + + if (var8 < 0) { + return 0; + } + + var7 = var8; + if (var8 >= this.mKeys.length) { + return this.mKeys.length - 1; + } + } else { + var7 = 0; + } + + return var7; + } + + public int getRowCount() { + return this.mRowCount; + } + + public int getShiftState() { + return this.mShiftState; + } + + public void invalidateAllKeys() { + this.createKeyImageViews(this.mKeys); + } + + public void invalidateKey(int keyIndex) { + if (this.mKeys != null && keyIndex >= 0 && keyIndex < this.mKeys.length) { + if (this.mKeyImageViews[keyIndex] != null) { + this.removeView(this.mKeyImageViews[keyIndex]); + } + + this.mKeyImageViews[keyIndex] = this.createKeyImageView(keyIndex); + } + } + + public boolean isMiniKeyboardOnScreen() { + return this.mMiniKeyboardOnScreen; + } + + public boolean isShifted() { + return this.mShiftState == 1 || this.mShiftState == 2; + } + + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + public void onKeyLongPress() { + int var1 = this.mKeys[this.mFocusIndex].key.popupResId; + if (var1 != 0) { + this.dismissMiniKeyboard(); + this.mMiniKeyboardOnScreen = true; + List var6 = (new Keyboard(this.getContext(), var1)).getKeys(); + int var3 = var6.size(); + var1 = this.mFocusIndex; + int var2 = this.mFocusIndex / this.mColCount; + int var4 = (this.mFocusIndex + var3) / this.mColCount; + if (var2 != var4) { + var1 = this.mColCount * var4 - var3; + } + + this.mBaseMiniKbIndex = var1; + + for (var2 = 0; var2 < var3; ++var2) { + Key var7 = (Key) var6.get(var2); + var7.x = this.mKeys[var1 + var2].key.x; + var7.y = this.mKeys[var1 + var2].key.y; + var7.edgeFlags = this.mKeys[var1 + var2].key.edgeFlags; + this.mKeys[var1 + var2].key = var7; + this.mKeys[var1 + var2].isInMiniKb = true; + LeanbackKeyboardView.KeyHolder var8 = this.mKeys[var1 + var2]; + boolean var5; + if (var2 == 0) { + var5 = true; + } else { + var5 = false; + } + + var8.isInvertible = var5; + } + + this.invalidateAllKeys(); + } + + } + + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (this.mKeyboard == null) { + this.setMeasuredDimension(this.getPaddingLeft() + this.getPaddingRight(), this.getPaddingTop() + this.getPaddingBottom()); + } else { + int var3 = this.mKeyboard.getMinWidth() + this.getPaddingLeft() + this.getPaddingRight(); + heightMeasureSpec = var3; + if (MeasureSpec.getSize(widthMeasureSpec) < var3 + 10) { + heightMeasureSpec = MeasureSpec.getSize(widthMeasureSpec); + } + + this.setMeasuredDimension(heightMeasureSpec, this.mKeyboard.getHeight() + this.getPaddingTop() + this.getPaddingBottom()); + } + } + + public void setFocus(int row, int col, boolean clicked) { + this.setFocus(this.mColCount * row + col, clicked); + } + + public void setFocus(int index, boolean clicked) { + this.setFocus(index, clicked, true); + } + + public void setFocus(int index, boolean clicked, boolean showFocusScale) { + float var4 = 1.0F; + if (this.mKeyImageViews != null && this.mKeyImageViews.length != 0) { + int var5; + label49: + { + if (index >= 0) { + var5 = index; + if (index < this.mKeyImageViews.length) { + break label49; + } + } + + var5 = -1; + } + + if (var5 != this.mFocusIndex || clicked != this.mFocusClicked) { + if (var5 != this.mFocusIndex) { + if (this.mFocusIndex != -1) { + LeanbackUtils.sendAccessibilityEvent(this.mKeyImageViews[this.mFocusIndex], false); + } + + if (var5 != -1) { + LeanbackUtils.sendAccessibilityEvent(this.mKeyImageViews[var5], true); + } + } + + if (this.mCurrentFocusView != null) { + this.mCurrentFocusView.animate().scaleX(1.0F).scaleY(1.0F).setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setStartDelay((long) this.mUnfocusStartDelay); + this.mCurrentFocusView.animate().setDuration((long) this.mClickAnimDur).setInterpolator(LeanbackKeyboardContainer + .sMovementInterpolator).setStartDelay((long) this.mUnfocusStartDelay); + } + + if (var5 != -1) { + if (clicked) { + var4 = this.mClickedScale; + } else if (showFocusScale) { + var4 = this.mFocusedScale; + } + + this.mCurrentFocusView = this.mKeyImageViews[var5]; + this.mCurrentFocusView.animate().scaleX(var4).scaleY(var4).setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setDuration((long) this.mClickAnimDur).start(); + } + + this.mFocusIndex = var5; + this.mFocusClicked = clicked; + if (-1 != var5 && !this.mKeys[var5].isInMiniKb) { + this.dismissMiniKeyboard(); + return; + } + } + } + + } + + public void setKeyboard(Keyboard keyboard) { + this.removeMessages(); + this.mKeyboard = keyboard; + this.setKeys(this.mKeyboard.getKeys()); + int var2 = this.mShiftState; + this.mShiftState = -1; + this.setShiftState(var2); + this.requestLayout(); + this.invalidateAllKeys(); + } + + public void setShiftState(int state) { + if (this.mShiftState != state) { + switch (state) { + case 0: + this.mKeyboard.setShifted(false); + break; + case 1: + case 2: + this.mKeyboard.setShifted(true); + } + + this.mShiftState = state; + this.invalidateAllKeys(); + } + } + + private class KeyHolder { + public boolean isInMiniKb = false; + public boolean isInvertible = false; + public Key key; + + public KeyHolder(Key var2) { + this.key = var2; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java new file mode 100644 index 0000000..f2797ca --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java @@ -0,0 +1,92 @@ +package com.google.android.leanback.ime; + +import java.util.Locale; + +public class LeanbackLocales { + public static final Locale ALBANIANIAN; + public static final Locale AZERBAIJANI; + public static final Locale[] AZERTY; + public static final Locale BASQUE_SPANISH; + public static final Locale BELGIAN_DUTCH; + public static final Locale BRITISH_ENGLISH = new Locale("en", "GB"); + public static final Locale CANADIAN_FRENCH; + public static final Locale CATALAN; + public static final Locale CROATIAN; + public static final Locale CZECH; + public static final Locale DANISH; + public static final Locale ENGLISH; + public static final Locale ESTONIAN; + public static final Locale FINNISH; + public static final Locale FRENCH; + public static final Locale GALIC_SPANISH; + public static final Locale GERMAN; + public static final Locale HUNGARIAN; + public static final Locale INDIAN_ENGLISH; + public static final Locale NORWEGIAN; + public static final Locale OTHER_SPANISH; + public static final Locale[] QWERTY_AZ; + public static final Locale[] QWERTY_CA; + public static final Locale[] QWERTY_DA; + public static final Locale[] QWERTY_ES_EU; + public static final Locale[] QWERTY_ES_US; + public static final Locale[] QWERTY_ET; + public static final Locale[] QWERTY_FI; + public static final Locale[] QWERTY_GB; + public static final Locale[] QWERTY_IN; + public static final Locale[] QWERTY_NB; + public static final Locale[] QWERTY_SV; + public static final Locale[] QWERTY_US; + public static final Locale[] QWERTZ; + public static final Locale[] QWERTZ_CH; + public static final Locale SERBIAN; + public static final Locale SLOVENIAN; + public static final Locale SPAIN_SPANISH; + public static final Locale SWEDISH; + public static final Locale SWISS_FRENCH; + public static final Locale SWISS_GERMAN; + public static final Locale SWISS_ITALIAN; + + static { + QWERTY_GB = new Locale[]{BRITISH_ENGLISH}; + INDIAN_ENGLISH = new Locale("en", "IN"); + QWERTY_IN = new Locale[]{INDIAN_ENGLISH}; + SPAIN_SPANISH = new Locale("es", "ES"); + GALIC_SPANISH = new Locale("gl", "ES"); + BASQUE_SPANISH = new Locale("eu", "ES"); + QWERTY_ES_EU = new Locale[]{SPAIN_SPANISH, GALIC_SPANISH, BASQUE_SPANISH}; + OTHER_SPANISH = new Locale("es", ""); + QWERTY_ES_US = new Locale[]{OTHER_SPANISH}; + AZERBAIJANI = new Locale("az", ""); + QWERTY_AZ = new Locale[]{AZERBAIJANI}; + CATALAN = new Locale("ca", ""); + QWERTY_CA = new Locale[]{CATALAN}; + DANISH = new Locale("da", ""); + QWERTY_DA = new Locale[]{DANISH}; + ESTONIAN = new Locale("et", ""); + QWERTY_ET = new Locale[]{ESTONIAN}; + FINNISH = new Locale("fi", ""); + QWERTY_FI = new Locale[]{FINNISH}; + NORWEGIAN = new Locale("nb", ""); + QWERTY_NB = new Locale[]{NORWEGIAN}; + SWEDISH = new Locale("sv", ""); + QWERTY_SV = new Locale[]{SWEDISH}; + ENGLISH = Locale.ENGLISH; + CANADIAN_FRENCH = Locale.CANADA_FRENCH; + QWERTY_US = new Locale[]{ENGLISH, CANADIAN_FRENCH}; + SWISS_GERMAN = new Locale("de", "CH"); + SWISS_ITALIAN = new Locale("it", "CH"); + QWERTZ_CH = new Locale[]{SWISS_GERMAN, SWISS_ITALIAN}; + GERMAN = new Locale("de", ""); + CROATIAN = new Locale("hr", ""); + CZECH = new Locale("cs", ""); + SWISS_FRENCH = new Locale("fr", "CH"); + HUNGARIAN = new Locale("hu", ""); + SERBIAN = new Locale("sr", ""); + SLOVENIAN = new Locale("sl", ""); + ALBANIANIAN = new Locale("sq", ""); + QWERTZ = new Locale[]{GERMAN, CROATIAN, CZECH, SWISS_FRENCH, SWISS_ITALIAN, HUNGARIAN, SERBIAN, SLOVENIAN, ALBANIANIAN}; + FRENCH = Locale.FRENCH; + BELGIAN_DUTCH = new Locale("nl", "BE"); + AZERTY = new Locale[]{FRENCH, BELGIAN_DUTCH}; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java new file mode 100644 index 0000000..36efd56 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java @@ -0,0 +1,94 @@ +package com.google.android.leanback.ime; + +import android.inputmethodservice.InputMethodService; +import android.text.TextUtils; +import android.util.Log; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; + +public class LeanbackSuggestionsFactory { + private static final boolean DEBUG = Log.isLoggable("LbSuggestionsFactory", Log.DEBUG); + private static final int MODE_AUTO_COMPLETE = 2; + private static final int MODE_DEFAULT = 0; + private static final int MODE_DOMAIN = 1; + private static final String TAG = "LbSuggestionsFactory"; + private InputMethodService mContext; + private int mMode; + private int mNumSuggestions; + private final ArrayList mSuggestions = new ArrayList(); + + public LeanbackSuggestionsFactory(InputMethodService var1, int var2) { + this.mContext = var1; + this.mNumSuggestions = var2; + } + + public void clearSuggestions() { + this.mSuggestions.clear(); + } + + public void createSuggestions() { + this.clearSuggestions(); + if (this.mMode == 1) { + String[] var3 = this.mContext.getResources().getStringArray(R.array.common_domains); + int var2 = var3.length; + + for(int var1 = 0; var1 < var2; ++var1) { + String var4 = var3[var1]; + this.mSuggestions.add(var4); + } + } + + } + + public ArrayList getSuggestions() { + return this.mSuggestions; + } + + public void onDisplayCompletions(CompletionInfo[] infos) { + this.createSuggestions(); + int len; + if (infos == null) { + len = 0; + } else { + len = infos.length; + } + + for(int i = 0; i < len && this.mSuggestions.size() < this.mNumSuggestions && !TextUtils.isEmpty(infos[i].getText()); ++i) { + this.mSuggestions.add(i, infos[i].getText().toString()); + } + + if (Log.isLoggable("LbSuggestionsFactory", Log.DEBUG)) { + for(len = 0; len < this.mSuggestions.size(); ++len) { + Log.d("LbSuggestionsFactory", "completion " + len + ": " + (String)this.mSuggestions.get(len)); + } + } + + } + + public void onStartInput(EditorInfo info) { + this.mMode = 0; + if ((info.inputType & 65536) != 0) { + this.mMode = 2; + } + + switch(LeanbackUtils.getInputTypeClass(info)) { + case 1: + switch(LeanbackUtils.getInputTypeVariation(info)) { + case 32: + case 208: + this.mMode = 1; + return; + default: + return; + } + default: + } + } + + public boolean shouldSuggestionsAmend() { + return this.mMode == 1; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java new file mode 100644 index 0000000..73e6b65 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java @@ -0,0 +1,38 @@ +package com.google.android.leanback.ime; + +import android.os.Handler; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +public class LeanbackUtils { + private static final int ACCESSIBILITY_DELAY_MS = 250; + private static final Handler sAccessibilityHandler = new Handler(); + + public static int getImeAction(EditorInfo var0) { + return var0.imeOptions & 1073742079; + } + + public static int getInputTypeClass(EditorInfo var0) { + return var0.inputType & 15; + } + + public static int getInputTypeVariation(EditorInfo var0) { + return var0.inputType & 4080; + } + + public static boolean isAlphabet(int var0) { + return Character.isLetter(var0); + } + + public static void sendAccessibilityEvent(final View var0, boolean var1) { + if (var0 != null && var1) { + sAccessibilityHandler.removeCallbacksAndMessages((Object)null); + sAccessibilityHandler.postDelayed(new Runnable() { + public void run() { + var0.sendAccessibilityEvent(16384); + } + }, 250L); + } + + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java new file mode 100644 index 0000000..bfcf7b7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java @@ -0,0 +1,195 @@ +package com.google.android.leanback.ime.voice; + +import android.animation.TimeAnimator; +import android.animation.TimeAnimator.TimeListener; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Paint.Style; +import android.util.AttributeSet; +import android.view.View; +import com.liskovsoft.leankeykeyboard.R; + +public class BitmapSoundLevelView extends View { + private static final boolean DEBUG = false; + private static final int MIC_LEVEL_GUIDELINE_OFFSET = 13; + private static final int MIC_PRIMARY_LEVEL_IMAGE_OFFSET = 3; + private static final String TAG = "BitmapSoundLevelsView"; + private TimeAnimator mAnimator; + private final int mCenterTranslationX; + private final int mCenterTranslationY; + private int mCurrentVolume; + private Rect mDestRect; + private final int mDisableBackgroundColor; + private final Paint mEmptyPaint; + private final int mEnableBackgroundColor; + private SpeechLevelSource mLevelSource; + private final int mMinimumLevelSize; + private Paint mPaint; + private int mPeakLevel; + private int mPeakLevelCountDown; + private final Bitmap mPrimaryLevel; + private final Bitmap mTrailLevel; + + public BitmapSoundLevelView(Context context) { + this(context, null); + } + + public BitmapSoundLevelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @TargetApi(16) + public BitmapSoundLevelView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.mEmptyPaint = new Paint(); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BitmapSoundLevelView, defStyleAttr, 0); + this.mEnableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_enabledBackgroundColor, Color.parseColor("#66FFFFFF")); + this.mDisableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_disabledBackgroundColor, -1); + boolean primaryLevelEnabled = false; + boolean peakLevelEnabled = false; + int primaryLevelId = 0; + if (styledAttrs.hasValue(R.styleable.BitmapSoundLevelView_primaryLevels)) { + primaryLevelId = styledAttrs.getResourceId(R.styleable.BitmapSoundLevelView_primaryLevels, R.drawable.vs_reactive_dark); + primaryLevelEnabled = true; + } + + int trailLevelId = 0; + if (styledAttrs.hasValue(R.styleable.BitmapSoundLevelView_trailLevels)) { + trailLevelId = styledAttrs.getResourceId(R.styleable.BitmapSoundLevelView_trailLevels, R.drawable.vs_reactive_light); + peakLevelEnabled = true; + } + + this.mCenterTranslationX = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterX, 0); + this.mCenterTranslationY = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterY, 0); + this.mMinimumLevelSize = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_minLevelRadius, 0); + styledAttrs.recycle(); + if (primaryLevelEnabled) { + this.mPrimaryLevel = BitmapFactory.decodeResource(this.getResources(), primaryLevelId); + } else { + this.mPrimaryLevel = null; + } + + if (peakLevelEnabled) { + this.mTrailLevel = BitmapFactory.decodeResource(this.getResources(), trailLevelId); + } else { + this.mTrailLevel = null; + } + + this.mPaint = new Paint(); + this.mDestRect = new Rect(); + this.mEmptyPaint.setFilterBitmap(true); + this.mLevelSource = new SpeechLevelSource(); + this.mLevelSource.setSpeechLevel(0); + this.mAnimator = new TimeAnimator(); + this.mAnimator.setRepeatCount(-1); + this.mAnimator.setTimeListener(new TimeListener() { + public void onTimeUpdate(TimeAnimator animator, long l, long l1) { + BitmapSoundLevelView.this.invalidate(); + } + }); + } + + @TargetApi(16) + private void startAnimator() { + if (!this.mAnimator.isStarted()) { + this.mAnimator.start(); + } + + } + + @TargetApi(16) + private void stopAnimator() { + this.mAnimator.cancel(); + } + + private void updateAnimatorState() { + if (this.isEnabled()) { + this.startAnimator(); + } else { + this.stopAnimator(); + } + } + + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + this.updateAnimatorState(); + } + + protected void onDetachedFromWindow() { + this.stopAnimator(); + super.onDetachedFromWindow(); + } + + public void onDraw(Canvas canvas) { + if (this.isEnabled()) { + canvas.drawColor(this.mEnableBackgroundColor); + final int level = this.mLevelSource.getSpeechLevel(); + if (level > this.mPeakLevel) { + this.mPeakLevel = level; + this.mPeakLevelCountDown = 25; + } else if (this.mPeakLevelCountDown == 0) { + this.mPeakLevel = Math.max(0, this.mPeakLevel - 2); + } else { + --this.mPeakLevelCountDown; + } + + if (level > this.mCurrentVolume) { + this.mCurrentVolume += (level - this.mCurrentVolume) / 4; + } else { + this.mCurrentVolume = (int) ((float) this.mCurrentVolume * 0.95F); + } + + final int centerX = this.mCenterTranslationX + this.getWidth() / 2; + final int centerY = this.mCenterTranslationY + this.getWidth() / 2; + int size; + if (this.mTrailLevel != null) { + size = (centerX - this.mMinimumLevelSize) * this.mPeakLevel / 100 + this.mMinimumLevelSize; + this.mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(this.mTrailLevel, (Rect) null, this.mDestRect, this.mEmptyPaint); + } + + if (this.mPrimaryLevel != null) { + size = (centerX - this.mMinimumLevelSize) * this.mCurrentVolume / 100 + this.mMinimumLevelSize; + this.mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(this.mPrimaryLevel, (Rect) null, this.mDestRect, this.mEmptyPaint); + this.mPaint.setColor(this.getResources().getColor(R.color.search_mic_background)); + this.mPaint.setStyle(Style.FILL); + canvas.drawCircle((float) centerX, (float) centerY, (float) (this.mMinimumLevelSize - 3), this.mPaint); + } + + if (this.mTrailLevel != null && this.mPrimaryLevel != null) { + this.mPaint.setColor(this.getResources().getColor(R.color.search_mic_levels_guideline)); + this.mPaint.setStyle(Style.STROKE); + canvas.drawCircle((float) centerX, (float) centerY, (float) (centerX - 13), this.mPaint); + } + + } else { + canvas.drawColor(this.mDisableBackgroundColor); + } + } + + public void onWindowFocusChanged(boolean var1) { + super.onWindowFocusChanged(var1); + if (var1) { + this.updateAnimatorState(); + } else { + this.stopAnimator(); + } + } + + public void setEnabled(boolean var1) { + super.setEnabled(var1); + this.updateAnimatorState(); + } + + public void setLevelSource(SpeechLevelSource var1) { + this.mLevelSource = var1; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java new file mode 100644 index 0000000..5d95f3e --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java @@ -0,0 +1,216 @@ +package com.google.android.leanback.ime.voice; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Parcelable.Creator; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.BaseSavedState; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import com.google.android.leanback.ime.LeanbackUtils; +import com.liskovsoft.leankeykeyboard.R; + +public class RecognizerView extends RelativeLayout { + private static final boolean DEBUG = false; + private static final String TAG = "RecognizerView"; + private RecognizerView.Callback mCallback; + private boolean mEnabled; + protected ImageView mMicButton; + private BitmapSoundLevelView mSoundLevels; + private RecognizerView.State mState; + + public RecognizerView(Context var1) { + super(var1); + } + + public RecognizerView(Context var1, AttributeSet var2) { + super(var1, var2); + } + + public RecognizerView(Context var1, AttributeSet var2, int var3) { + super(var1, var2, var3); + } + + private void updateState(RecognizerView.State var1) { + this.mState = var1; + this.refreshUi(); + } + + public View getMicButton() { + return this.mMicButton; + } + + public void onAttachedToWindow() { + super.onAttachedToWindow(); + this.refreshUi(); + } + + public void onClick() { + switch (this.mState) { + case MIC_INITIALIZING: + default: + return; + case LISTENING: + this.mCallback.onCancelRecordingClicked(); + return; + case RECORDING: + this.mCallback.onStopRecordingClicked(); + return; + case RECOGNIZING: + this.mCallback.onCancelRecordingClicked(); + return; + case NOT_LISTENING: + this.mCallback.onStartRecordingClicked(); + } + } + + @SuppressLint("MissingSuperCall") + @Override + public void onFinishInflate() { + LayoutInflater.from(this.getContext()).inflate(R.layout.recognizer_view, this, true); + this.mSoundLevels = (BitmapSoundLevelView) this.findViewById(R.id.microphone); + this.mMicButton = (ImageView) this.findViewById(R.id.recognizer_mic_button); + this.mState = RecognizerView.State.NOT_LISTENING; + } + + @Override + public void onRestoreInstanceState(Parcelable var1) { + if (!(var1 instanceof RecognizerView.SavedState)) { + super.onRestoreInstanceState(var1); + } else { + RecognizerView.SavedState var2 = (RecognizerView.SavedState) var1; + super.onRestoreInstanceState(var2.getSuperState()); + this.mState = var2.mState; + } + } + + @Override + public Parcelable onSaveInstanceState() { + RecognizerView.SavedState var1 = new RecognizerView.SavedState(super.onSaveInstanceState()); + var1.mState = this.mState; + return var1; + } + + protected void refreshUi() { + if (this.mEnabled) { + switch (this.mState) { + case MIC_INITIALIZING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + this.mSoundLevels.setEnabled(false); + return; + case LISTENING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + this.mSoundLevels.setEnabled(true); + return; + case RECORDING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector); + this.mSoundLevels.setEnabled(true); + return; + case RECOGNIZING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + this.mSoundLevels.setEnabled(false); + return; + case NOT_LISTENING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + this.mSoundLevels.setEnabled(false); + return; + default: + } + } + } + + public void setCallback(RecognizerView.Callback callback) { + this.mCallback = callback; + } + + public void setMicEnabled(boolean enabled) { + this.mEnabled = enabled; + if (enabled) { + this.mMicButton.setAlpha(1.0F); + this.mMicButton.setImageResource(R.drawable.ic_voice_available); + } else { + this.mMicButton.setAlpha(0.1F); + this.mMicButton.setImageResource(R.drawable.ic_voice_off); + } + } + + public void setMicFocused(boolean focused) { + if (this.mEnabled) { + if (focused) { + this.mMicButton.setImageResource(R.drawable.ic_voice_focus); + } else { + this.mMicButton.setImageResource(R.drawable.ic_voice_available); + } + + LeanbackUtils.sendAccessibilityEvent(this.mMicButton, focused); + } + + } + + public void setSpeechLevelSource(SpeechLevelSource var1) { + this.mSoundLevels.setLevelSource(var1); + } + + public void showInitializingMic() { + this.updateState(RecognizerView.State.MIC_INITIALIZING); + } + + public void showListening() { + this.updateState(RecognizerView.State.LISTENING); + } + + public void showNotListening() { + this.updateState(RecognizerView.State.NOT_LISTENING); + } + + public void showRecognizing() { + this.updateState(RecognizerView.State.RECOGNIZING); + } + + public void showRecording() { + this.updateState(RecognizerView.State.RECORDING); + } + + public interface Callback { + void onCancelRecordingClicked(); + + void onStartRecordingClicked(); + + void onStopRecordingClicked(); + } + + public static class SavedState extends BaseSavedState { + public static final Creator CREATOR = new Creator() { + public RecognizerView.SavedState createFromParcel(Parcel var1) { + return new RecognizerView.SavedState(var1); + } + + public RecognizerView.SavedState[] newArray(int var1) { + return new RecognizerView.SavedState[var1]; + } + }; + RecognizerView.State mState; + + private SavedState(Parcel var1) { + super(var1); + this.mState = RecognizerView.State.valueOf(var1.readString()); + } + + public SavedState(Parcelable var1) { + super(var1); + } + + public void writeToParcel(Parcel var1, int var2) { + super.writeToParcel(var1, var2); + var1.writeString(this.mState.toString()); + } + } + + private static enum State { + LISTENING, MIC_INITIALIZING, NOT_LISTENING, RECOGNIZING, RECORDING; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java new file mode 100644 index 0000000..99bcd09 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java @@ -0,0 +1,25 @@ +package com.google.android.leanback.ime.voice; + +public class SpeechLevelSource { + private volatile int mSpeechLevel; + + public int getSpeechLevel() { + return this.mSpeechLevel; + } + + public boolean isValid() { + return this.mSpeechLevel > 0; + } + + public void reset() { + this.mSpeechLevel = -1; + } + + public void setSpeechLevel(int var1) { + if (var1 >= 0 && var1 <= 100) { + this.mSpeechLevel = var1; + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java new file mode 100644 index 0000000..eba1561 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java @@ -0,0 +1,164 @@ +package com.google.android.pano.util; + +import android.annotation.SuppressLint; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.InputDevice.MotionRange; + +public class TouchNavMotionTracker { + private static final float MAXIMUM_FLING_VELOCITY = 1270.0F; + private static final float MINIMUM_FLING_VELOCITY = 200.0F; + private float mCurrX; + private float mCurrY; + private MotionEvent mDownEvent; + private final float mMaxFlingVelocityX; + private final float mMaxFlingVelocityY; + private final float mMinFlingVelocityX; + private final float mMinFlingVelocityY; + private final float mMinScrollX; + private final float mMinScrollY; + private float mPrevX; + private float mPrevY; + private final float mResolutionX; + private final float mResolutionY; + private float mScrollX; + private float mScrollY; + private float mVelX; + private float mVelY; + private VelocityTracker mVelocityTracker; + + public TouchNavMotionTracker(float resolutionX, float resolutionY, float minScrollDist) { + if (resolutionX <= 0.0F) { + resolutionX = 6.3F; + } + + this.mResolutionX = resolutionX; + if (resolutionY <= 0.0F) { + resolutionY = 6.3F; + } + + this.mResolutionY = resolutionY; + this.mMaxFlingVelocityX = this.mResolutionX * 1270.0F; + this.mMaxFlingVelocityY = this.mResolutionY * 1270.0F; + this.mMinFlingVelocityX = this.mResolutionX * 200.0F; + this.mMinFlingVelocityY = this.mResolutionY * 200.0F; + this.mMinScrollX = this.mResolutionX * minScrollDist; + this.mMinScrollY = this.mResolutionY * minScrollDist; + } + + @SuppressLint("NewApi") + public static TouchNavMotionTracker buildTrackerForDevice(final InputDevice device, final float minScrollDist) { + MotionRange range = device.getMotionRange(0); + float resolution; + if (range == null) { + resolution = 0.0F; + } else { + resolution = range.getResolution(); + } + + float resolutionX = resolution; + if (resolution <= 0.0F) { + resolutionX = 6.3F; + } + + MotionRange range2 = device.getMotionRange(1); + if (range2 == null) { + resolution = 0.0F; + } else { + resolution = range2.getResolution(); + } + + float resolutionY = resolution; + if (resolution <= 0.0F) { + resolutionY = 6.3F; + } + + return new TouchNavMotionTracker(resolutionX, resolutionY, minScrollDist); + } + + public void addMovement(MotionEvent var1) { + if (this.mVelocityTracker == null) { + this.mVelocityTracker = VelocityTracker.obtain(); + } + + this.mVelocityTracker.addMovement(var1); + } + + public void clear() { + if (this.mDownEvent != null) { + this.mDownEvent.recycle(); + this.mDownEvent = null; + } + + if (this.mVelocityTracker != null) { + this.mVelocityTracker.recycle(); + this.mVelocityTracker = null; + } + + } + + public boolean computeVelocity() { + this.mVelocityTracker.computeCurrentVelocity(1000); + this.mVelX = Math.min(this.mMaxFlingVelocityX, this.mVelocityTracker.getXVelocity()); + this.mVelY = Math.min(this.mMaxFlingVelocityY, this.mVelocityTracker.getYVelocity()); + return Math.abs(this.mVelX) > this.mMinFlingVelocityX || Math.abs(this.mVelY) > this.mMinFlingVelocityY; + } + + public MotionEvent getDownEvent() { + return this.mDownEvent; + } + + public float getPhysicalX(float var1) { + return var1 / this.mResolutionX; + } + + public float getPhysicalY(float var1) { + return var1 / this.mResolutionY; + } + + public float getScrollX() { + return this.mScrollX; + } + + public float getScrollY() { + return this.mScrollY; + } + + public float getXResolution() { + return this.mResolutionX; + } + + public float getXVel() { + return this.mVelX; + } + + public float getYResolution() { + return this.mResolutionY; + } + + public float getYVel() { + return this.mVelY; + } + + public void setDownEvent(MotionEvent var1) { + if (this.mDownEvent != null && var1 != this.mDownEvent) { + this.mDownEvent.recycle(); + } + + this.mDownEvent = var1; + } + + public boolean setNewValues(float var1, float var2) { + this.mCurrX = var1; + this.mCurrY = var2; + this.mScrollX = this.mCurrX - this.mPrevX; + this.mScrollY = this.mCurrY - this.mPrevY; + return Math.abs(this.mScrollX) > this.mMinScrollX || Math.abs(this.mScrollY) > this.mMinScrollY; + } + + public void updatePrevValues() { + this.mPrevX = this.mCurrX; + this.mPrevY = this.mCurrY; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java new file mode 100644 index 0000000..b9a7597 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java @@ -0,0 +1,593 @@ +package com.google.android.pano.util; + +import android.animation.TimeInterpolator; +import android.graphics.PointF; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.util.SparseArray; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.animation.AccelerateInterpolator; + +public class TouchNavSpaceTracker { + private static final boolean DEBUG = false; + public static final float DEFAULT_DAMPED_SENSITIVITY = 0.5F; + public static final long DEFAULT_DAMPENING_DURATION_MS = 200L; + public static final long DEFAULT_DAMPING_DURATION_MS = 200L; + public static final float DEFAULT_HORIZONTAL_SIZE_MM = 120.0F; + public static final float DEFAULT_LPF_COEFF = 0.25F; + public static final float DEFAULT_MAX_FLICK_DISTANCE_MM = 40.0F; + public static final long DEFAULT_MAX_FLICK_DURATION_MS = 250L; + public static final float DEFAULT_MIN_FLICK_DISTANCE_MM = 4.0F; + public static final float DEFAULT_SENSITIVITY = 1.0F; + public static final float DEFAULT_VERTICAL_SIZE_MM = 50.0F; + private static final int[] DIRECTIONS = new int[]{1, 3, 2, 6, 4, 12, 8, 9, 1}; + private static final float[] DIRECTION_BOUNDARIES = new float[]{-2.7488935F, -1.9634954F, -1.1780972F, -0.3926991F, 0.3926991F, 1.1780972F, 1.9634954F, 2.7488935F}; + public static final int DIRECTION_DOWN = 2; + public static final int DIRECTION_DOWN_LEFT = 3; + public static final int DIRECTION_DOWN_RIGHT = 6; + public static final int DIRECTION_LEFT = 1; + public static final int DIRECTION_RIGHT = 4; + public static final int DIRECTION_UP = 8; + public static final int DIRECTION_UP_LEFT = 9; + public static final int DIRECTION_UP_RIGHT = 12; + private static final int MSG_LONG_CLICK = 0; + private static final String TAG = "TouchNavSpaceTracker"; + private float mDampedSensitivity; + private float mDampingDuration; + private float mFlickMaxDistance; + private long mFlickMaxDuration; + private float mFlickMaxSquared; + private float mFlickMinDistance; + private float mFlickMinSquared; + private Handler mHandler; + protected TouchNavSpaceTracker.KeyEventListener mKeyEventListener; + private float mLPFCurrX; + private float mLPFCurrY; + private boolean mLPFEnabled; + private long mMovementBlockTime; + private float mPhysicalHeight; + private PointF mPhysicalPosition; + private float mPhysicalWidth; + private float mPixelHeight; + protected TouchNavSpaceTracker.TouchEventListener mPixelListener; + private float mPixelWidth; + private float mPixelsPerMm; + private PointF mPrevPhysPosition; + private float mSensitivity; + private TimeInterpolator mSensitivityInterpolator; + protected final SparseArray mTouchParams; + private float mUnscaledFlickMaxDistance; + private float mUnscaledFlickMinDistance; + private boolean mWasBlocked; + + public TouchNavSpaceTracker() { + this((TouchNavSpaceTracker.KeyEventListener)null, (TouchNavSpaceTracker.TouchEventListener)null); + } + + public TouchNavSpaceTracker(TouchNavSpaceTracker.KeyEventListener var1, TouchNavSpaceTracker.TouchEventListener var2) { + this.mPrevPhysPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + this.mPhysicalPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + this.mWasBlocked = false; + this.mSensitivityInterpolator = new AccelerateInterpolator(); + this.mDampingDuration = 200.0F; + this.mDampedSensitivity = 0.5F; + this.mSensitivity = 1.0F; + this.mUnscaledFlickMinDistance = 4.0F; + this.mUnscaledFlickMaxDistance = 40.0F; + this.mFlickMinDistance = this.mSensitivity * 4.0F; + this.mFlickMaxDistance = this.mSensitivity * 40.0F; + this.mFlickMinSquared = this.mFlickMinDistance * this.mFlickMinDistance; + this.mFlickMaxSquared = this.mFlickMaxDistance * this.mFlickMaxDistance; + this.mFlickMaxDuration = 250L; + this.mLPFEnabled = false; + this.mHandler = new Handler() { + public void handleMessage(Message var1) { + switch(var1.what) { + case 0: + if (TouchNavSpaceTracker.this.mKeyEventListener != null) { + TouchNavSpaceTracker.this.mKeyEventListener.onKeyLongPress(var1.arg1, (KeyEvent)var1.obj); + return; + } + default: + } + } + }; + this.mKeyEventListener = var1; + this.mPixelListener = var2; + this.mTouchParams = new SparseArray(1); + this.mPhysicalWidth = 120.0F; + this.mPhysicalHeight = 50.0F; + this.mPixelWidth = 0.0F; + this.mPixelHeight = 0.0F; + this.mPixelsPerMm = 0.0F; + } + + private float calculateSensitivity(MotionEvent var1, MotionEvent var2) { + long var4 = var1.getEventTime() - var2.getEventTime(); + float var3; + if (var1.getEventTime() < this.mMovementBlockTime) { + var3 = 0.0F; + this.mWasBlocked = true; + } else if ((float)var4 < this.mDampingDuration) { + var3 = this.mSensitivityInterpolator.getInterpolation((float)var4 / this.mDampingDuration); + var3 = this.mDampedSensitivity + (this.mSensitivity - this.mDampedSensitivity) * var3; + } else { + var3 = this.mSensitivity; + } + + if (var3 != 0.0F && this.mWasBlocked) { + this.mWasBlocked = false; + this.setPhysicalPosition(this.mPhysicalPosition.x, this.mPhysicalPosition.y); + } + + return var3; + } + + private void checkForLongClick(int var1, KeyEvent var2) { + if (var1 == 23) { + Message var3 = this.mHandler.obtainMessage(0); + var3.arg1 = var1; + var3.obj = var2; + if (!this.mHandler.hasMessages(0)) { + this.mHandler.sendMessageDelayed(var3, (long)ViewConfiguration.getLongPressTimeout()); + return; + } + } + + } + + private void clampPosition() { + if (this.mPhysicalPosition.x < 0.0F) { + this.setPhysicalPosition(0.0F, this.mPhysicalPosition.y); + } else if (this.mPhysicalPosition.x > this.mPhysicalWidth) { + this.setPhysicalPosition(this.mPhysicalWidth, this.mPhysicalPosition.y); + } + + if (this.mPhysicalPosition.y < 0.0F) { + this.setPhysicalPosition(this.mPhysicalPosition.x, 0.0F); + } else if (this.mPhysicalPosition.y > this.mPhysicalHeight) { + this.setPhysicalPosition(this.mPhysicalPosition.x, this.mPhysicalHeight); + return; + } + + } + + private int getDpadDirection(float var1, float var2) { + var1 = (float)Math.atan2((double)(-var2), (double)var1); + + int var3; + for(var3 = 0; var3 < DIRECTION_BOUNDARIES.length && var1 >= DIRECTION_BOUNDARIES[var3]; ++var3) { + ; + } + + return DIRECTIONS[var3]; + } + + private float getPhysicalX(float var1) { + return this.mPixelWidth <= 0.0F ? 0.0F : this.mPhysicalWidth * var1 / this.mPixelWidth; + } + + private float getPhysicalY(float var1) { + return this.mPixelHeight <= 0.0F ? 0.0F : this.mPhysicalHeight * var1 / this.mPixelHeight; + } + + private float getPixelX(float var1) { + return this.mPixelWidth * var1 / this.mPhysicalWidth; + } + + private float getPixelY(float var1) { + return this.mPixelHeight * var1 / this.mPhysicalHeight; + } + + private int getPrimaryDpadDirection(float var1, float var2) { + if (Math.abs(var1) > Math.abs(var2)) { + return var1 > 0.0F ? 4 : 1; + } else { + return var2 > 0.0F ? 2 : 8; + } + } + + private float getScaledValue(float var1, float var2) { + return var1 * var2; + } + + private TouchNavMotionTracker getTrackerForDevice(InputDevice var1) { + TouchNavMotionTracker var3 = (TouchNavMotionTracker)this.mTouchParams.get(var1.getId()); + TouchNavMotionTracker var2 = var3; + if (var3 == null) { + var2 = TouchNavMotionTracker.buildTrackerForDevice(var1, 0.1F); + this.mTouchParams.put(var1.getId(), var2); + } + + return var2; + } + + private void setPhysicalSizeInternal(float var1, float var2) { + this.mPhysicalWidth = var1; + this.mPhysicalHeight = var2; + if (this.mPhysicalPosition.x > this.mPhysicalWidth) { + this.mPhysicalPosition.x = this.mPhysicalWidth; + } + + if (this.mPhysicalPosition.y > this.mPhysicalHeight) { + this.mPhysicalPosition.y = this.mPhysicalHeight; + } + + } + + private void updatePhysicalSize() { + if (this.mPixelWidth > 0.0F && this.mPixelHeight > 0.0F && this.mPixelsPerMm > 0.0F) { + this.setPhysicalSizeInternal(this.mPixelWidth / this.mPixelsPerMm, this.mPixelHeight / this.mPixelsPerMm); + } + + } + + public void blockMovementUntil(long var1) { + this.mMovementBlockTime = var1; + } + + public void configureDamping(float var1, long var2) { + this.mDampedSensitivity = var1; + this.mDampingDuration = (float)var2; + } + + public void configureFlicks(float var1, float var2, long var3) { + this.mUnscaledFlickMinDistance = var1; + this.mUnscaledFlickMaxDistance = var2; + this.mFlickMinDistance = this.mSensitivity * var1; + this.mFlickMaxDistance = this.mSensitivity * var2; + this.mFlickMinSquared = this.mFlickMinDistance * this.mFlickMinDistance; + this.mFlickMaxSquared = this.mFlickMaxDistance * this.mFlickMaxDistance; + this.mFlickMaxDuration = var3; + } + + public PointF getCurrentPhysicalPosition() { + return new PointF(this.mPhysicalPosition.x, this.mPhysicalPosition.y); + } + + public PointF getCurrentPixelPosition() { + return new PointF(this.getPixelX(this.mPhysicalPosition.x), this.getPixelY(this.mPhysicalPosition.y)); + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + if (var1 != null && (var1.getSource() & 2097152) == 2097152) { + InputDevice var13 = var1.getDevice(); + if (var13 == null) { + return false; + } + + TouchNavMotionTracker var19 = this.getTrackerForDevice(var13); + int var10 = var1.getActionMasked(); + var19.addMovement(var1); + boolean var6; + if ((var10 & 255) == 6) { + var6 = true; + } else { + var6 = false; + } + + int var7; + if (var6) { + var7 = var1.getActionIndex(); + } else { + var7 = -1; + } + + float var3 = 0.0F; + float var2 = 0.0F; + int var9 = var1.getPointerCount(); + + for(int var8 = 0; var8 < var9; ++var8) { + if (var7 != var8) { + var3 += var1.getX(var8); + var2 += var1.getY(var8); + } + } + + int var17; + if (var6) { + var17 = var9 - 1; + } else { + var17 = var9; + } + + float var4 = var3 / (float)var17; + float var5 = var2 / (float)var17; + TouchNavSpaceTracker.PhysicalMotionEvent var14 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var4), var19.getPhysicalX(var5), var1.getEventTime()); + boolean var18 = false; + boolean var12 = false; + boolean var11; + MotionEvent var15; + TouchNavSpaceTracker.PhysicalMotionEvent var16; + switch(var10 & 255) { + case 0: + if (this.mLPFEnabled) { + this.mLPFCurrX = var4; + this.mLPFCurrY = var5; + } + + var19.setNewValues(var4, var5); + var19.updatePrevValues(); + var19.setDownEvent(MotionEvent.obtain(var1)); + if (this.mPixelListener != null) { + return false | this.mPixelListener.onDown(var14); + } + break; + case 1: + var15 = var19.getDownEvent(); + if (var15 == null) { + Log.w("TouchNavSpaceTracker", "Up event without down event"); + return false | this.mPixelListener.onUp(var14, this.getPixelX(this.mPhysicalPosition.x), this.getPixelY(this.mPhysicalPosition.y)); + } + + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var15.getX()), var19.getPhysicalY(var15.getY()), var15.getEventTime()); + var6 = var18; + if (var19.computeVelocity()) { + var6 = var18; + if (this.mPixelListener != null) { + var2 = this.getPixelX(var19.getPhysicalX(var19.getXVel())); + var3 = this.getPixelY(var19.getPhysicalY(var19.getYVel())); + var18 = false | this.mPixelListener.onFling(var16, var14, var2, var3); + var6 = var18; + if (var14.getTime() - var16.getTime() < this.mFlickMaxDuration) { + var2 = var14.getX() - var16.getX(); + var3 = var14.getY() - var16.getY(); + var4 = var2 * var2 + var3 * var3; + var6 = var18; + if (var4 > this.mFlickMinSquared) { + var6 = var18; + if (var4 < this.mFlickMaxSquared) { + this.mPixelListener.onFlick(var16, var14, this.getDpadDirection(var2, var3), this.getPrimaryDpadDirection(var2, var3)); + var6 = var18; + } + } + } + } + } + + var2 = this.getPixelX(this.mPhysicalPosition.x); + var3 = this.getPixelY(this.mPhysicalPosition.y); + var11 = this.mPixelListener.onUp(var14, var2, var3); + var19.clear(); + return var6 | var11; + case 2: + if (var19.getDownEvent() == null) { + var19.setDownEvent(MotionEvent.obtain(var1)); + if (this.mLPFEnabled) { + this.mLPFCurrX = var4; + this.mLPFCurrY = var5; + } + } + + var3 = var4; + var2 = var5; + if (this.mLPFEnabled) { + this.mLPFCurrX = this.mLPFCurrX * 0.75F + 0.25F * var4; + this.mLPFCurrY = this.mLPFCurrY * 0.75F + 0.25F * var5; + var3 = this.mLPFCurrX; + var2 = this.mLPFCurrY; + } + + if (var19.setNewValues(var3, var2)) { + var2 = var19.getPhysicalX(var19.getScrollX()); + var3 = var19.getPhysicalY(var19.getScrollY()); + var4 = this.calculateSensitivity(var1, var19.getDownEvent()); + this.mPhysicalPosition.x = this.mPrevPhysPosition.x + this.getScaledValue(var2, var4); + this.mPhysicalPosition.y = this.mPrevPhysPosition.y + this.getScaledValue(var3, var4); + this.clampPosition(); + if (!this.mPhysicalPosition.equals(this.mPrevPhysPosition)) { + var11 = var12; + if (this.mPixelListener != null) { + var11 = var12; + if (this.mPixelHeight > 0.0F) { + var11 = var12; + if (this.mPixelWidth > 0.0F) { + var15 = var19.getDownEvent(); + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var15.getX()), var19.getPhysicalY(var15.getY()), var15.getEventTime()); + var2 = this.getPixelX(this.mPhysicalPosition.x); + var3 = this.getPixelY(this.mPhysicalPosition.y); + var11 = false | this.mPixelListener.onMove(var16, var14, var2, var3); + } + } + } + + this.mPrevPhysPosition.set(this.mPhysicalPosition); + } else { + var11 = false | true; + } + + var19.updatePrevValues(); + return var11; + } + + return false | true; + case 3: + var19.clear(); + return false; + default: + return false; + } + } + + return false; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + if (var2 != null && var2.getDevice() != null && (var2.getDevice().getSources() & 2097152) == 2097152) { + if (var2.getRepeatCount() == 0) { + this.checkForLongClick(var1, var2); + } + + if (this.mKeyEventListener != null) { + return this.mKeyEventListener.onKeyDown(var1, var2); + } + } + + return false; + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (var2 != null && var2.getDevice() != null && (var2.getDevice().getSources() & 2097152) == 2097152) { + if (var1 == 23) { + this.mHandler.removeMessages(0); + } + + if (this.mKeyEventListener != null) { + return this.mKeyEventListener.onKeyUp(var1, var2); + } + } + + return false; + } + + public void onPause() { + this.mHandler.removeMessages(0); + } + + public void setKeyEventListener(TouchNavSpaceTracker.KeyEventListener var1) { + this.mKeyEventListener = var1; + } + + public void setLPFEnabled(boolean var1) { + this.mLPFEnabled = var1; + } + + public void setPhysicalDensity(float var1) { + this.mPixelsPerMm = var1; + if (var1 > 0.0F) { + this.updatePhysicalSize(); + } + + } + + public void setPhysicalPosition(float var1, float var2) { + this.mPhysicalPosition.x = var1; + this.mPhysicalPosition.y = var2; + this.mPrevPhysPosition.x = var1; + this.mPrevPhysPosition.y = var2; + this.clampPosition(); + } + + public void setPhysicalSize(float var1, float var2) { + if (this.mPixelsPerMm <= 0.0F) { + this.setPhysicalSizeInternal(var1, var2); + } + } + + public void setPixelPosition(float var1, float var2) { + this.setPhysicalPosition(this.getPhysicalX(var1), this.getPhysicalY(var2)); + } + + public void setPixelSize(float var1, float var2) { + this.mPixelHeight = var2; + this.mPixelWidth = var1; + this.updatePhysicalSize(); + } + + public void setSensitivity(float var1) { + this.mSensitivity = var1; + this.configureFlicks(this.mUnscaledFlickMinDistance, this.mUnscaledFlickMaxDistance, this.mFlickMaxDuration); + } + + public void setTouchEventListener(TouchNavSpaceTracker.TouchEventListener var1) { + this.mPixelListener = var1; + } + + public void unblockMovement() { + this.mMovementBlockTime = 0L; + } + + public interface KeyEventListener { + boolean onKeyDown(int var1, KeyEvent var2); + + boolean onKeyLongPress(int var1, KeyEvent var2); + + boolean onKeyUp(int var1, KeyEvent var2); + } + + public static class PhysicalMotionEvent { + private final int mDeviceId; + private final long mTime; + // $FF: renamed from: mX float + private final float field_6; + // $FF: renamed from: mY float + private final float field_7; + + public PhysicalMotionEvent(int var1, float var2, float var3, long var4) { + this.mDeviceId = var1; + this.field_6 = var2; + this.field_7 = var3; + this.mTime = var4; + } + + public final InputDevice getDevice() { + return InputDevice.getDevice(this.getDeviceId()); + } + + public final int getDeviceId() { + return this.mDeviceId; + } + + public final long getTime() { + return this.mTime; + } + + public final float getX() { + return this.field_6; + } + + public final float getY() { + return this.field_7; + } + } + + public static class SimpleTouchEventListener implements TouchNavSpaceTracker.KeyEventListener, TouchNavSpaceTracker.TouchEventListener { + public boolean onDown(TouchNavSpaceTracker.PhysicalMotionEvent var1) { + return false; + } + + public boolean onFlick(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, int var3, int var4) { + return false; + } + + public boolean onFling(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4) { + return false; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + return false; + } + + public boolean onKeyLongPress(int var1, KeyEvent var2) { + return false; + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + return false; + } + + public boolean onMove(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4) { + return false; + } + + public boolean onUp(TouchNavSpaceTracker.PhysicalMotionEvent var1, float var2, float var3) { + return false; + } + } + + public interface TouchEventListener { + boolean onDown(TouchNavSpaceTracker.PhysicalMotionEvent var1); + + boolean onFlick(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, int var3, int var4); + + boolean onFling(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4); + + boolean onMove(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4); + + boolean onUp(TouchNavSpaceTracker.PhysicalMotionEvent var1, float var2, float var3); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java b/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java new file mode 100644 index 0000000..f924b8a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java @@ -0,0 +1,305 @@ +package com.google.leanback.ime; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.inputmethodservice.InputMethodService; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import com.google.android.leanback.ime.LeanbackKeyboardController; +import com.google.android.leanback.ime.LeanbackSuggestionsFactory; +import com.google.android.leanback.ime.LeanbackUtils; + +public class LeanbackImeService extends InputMethodService { + private static final boolean DEBUG = false; + public static final String IME_CLOSE = "com.google.android.athome.action.IME_CLOSE"; + public static final String IME_OPEN = "com.google.android.athome.action.IME_OPEN"; + public static final int MAX_SUGGESTIONS = 10; + static final int MODE_FREE_MOVEMENT = 1; + static final int MODE_TRACKPAD_NAVIGATION = 0; + private static final int MSG_SUGGESTIONS_CLEAR = 123; + private static final int SUGGESTIONS_CLEAR_DELAY = 1000; + private static final String TAG = "LbImeService"; + private boolean mEnterSpaceBeforeCommitting; + private final Handler mHandler = new Handler() { + public void handleMessage(Message var1) { + if (var1.what == 123 && LeanbackImeService.this.mShouldClearSuggestions) { + LeanbackImeService.this.mSuggestionsFactory.clearSuggestions(); + LeanbackImeService.this.mKeyboardController.updateSuggestions(LeanbackImeService.this.mSuggestionsFactory.getSuggestions()); + LeanbackImeService.this.mShouldClearSuggestions = false; + } + + } + }; + private LeanbackKeyboardController.InputListener mInputListener = new LeanbackKeyboardController.InputListener() { + public void onEntry(int var1, int var2, CharSequence var3) { + LeanbackImeService.this.handleTextEntry(var1, var2, var3); + } + }; + private View mInputView; + private LeanbackKeyboardController mKeyboardController; + private boolean mShouldClearSuggestions = true; + private LeanbackSuggestionsFactory mSuggestionsFactory; + + @SuppressLint("NewApi") + public LeanbackImeService() { + if (!this.enableHardwareAcceleration()) { + Log.w("LbImeService", "Could not enable hardware acceleration"); + } + + } + + private void clearSuggestionsDelayed() { + if (!this.mSuggestionsFactory.shouldSuggestionsAmend()) { + this.mHandler.removeMessages(123); + this.mShouldClearSuggestions = true; + this.mHandler.sendEmptyMessageDelayed(123, 1000L); + } + + } + + private int getAmpersandLocation(InputConnection var1) { + String var4 = this.getEditorText(var1); + int var3 = var4.indexOf(64); + int var2 = var3; + if (var3 < 0) { + var2 = var4.length(); + } + + return var2; + } + + private int getCharLengthAfterCursor(InputConnection var1) { + int var2 = 0; + CharSequence var3 = var1.getTextAfterCursor(1000, 0); + if (var3 != null) { + var2 = var3.length(); + } + + return var2; + } + + private int getCharLengthBeforeCursor(InputConnection var1) { + int var2 = 0; + CharSequence var3 = var1.getTextBeforeCursor(1000, 0); + if (var3 != null) { + var2 = var3.length(); + } + + return var2; + } + + private String getEditorText(InputConnection var1) { + StringBuilder var2 = new StringBuilder(); + CharSequence var3 = var1.getTextBeforeCursor(1000, 0); + CharSequence var4 = var1.getTextAfterCursor(1000, 0); + if (var3 != null) { + var2.append(var3); + } + + if (var4 != null) { + var2.append(var4); + } + + return var2.toString(); + } + + private void handleTextEntry(int var1, int var2, CharSequence var3) { + InputConnection var5 = this.getCurrentInputConnection(); + boolean var4 = true; + if (var5 != null) { + boolean var6; + switch (var1) { + case 0: + this.clearSuggestionsDelayed(); + if (this.mEnterSpaceBeforeCommitting && this.mKeyboardController.enableAutoEnterSpace()) { + if (LeanbackUtils.isAlphabet(var2)) { + var5.commitText(" ", 1); + } + + this.mEnterSpaceBeforeCommitting = false; + } + + var5.commitText(var3, 1); + var6 = var4; + if (var2 == 46) { + this.mEnterSpaceBeforeCommitting = true; + var6 = var4; + } + break; + case 1: + this.clearSuggestionsDelayed(); + var5.deleteSurroundingText(1, 0); + this.mEnterSpaceBeforeCommitting = false; + var6 = var4; + break; + case 2: + case 6: + this.clearSuggestionsDelayed(); + if (!this.mSuggestionsFactory.shouldSuggestionsAmend()) { + var5.deleteSurroundingText(this.getCharLengthBeforeCursor(var5), this.getCharLengthAfterCursor(var5)); + } else { + var1 = this.getAmpersandLocation(var5); + var5.setSelection(var1, var1); + var5.deleteSurroundingText(0, this.getCharLengthAfterCursor(var5)); + } + + var5.commitText(var3, 1); + this.mEnterSpaceBeforeCommitting = true; + case 5: + this.sendDefaultEditorAction(false); + var6 = false; + break; + case 3: + case 4: + var3 = var5.getTextBeforeCursor(1000, 0); + if (var3 == null) { + var2 = 0; + } else { + var2 = var3.length(); + } + + if (var1 == 3) { + var1 = var2; + if (var2 > 0) { + var1 = var2 - 1; + } + } else { + var3 = var5.getTextAfterCursor(1000, 0); + var1 = var2; + if (var3 != null) { + var1 = var2; + if (var3.length() > 0) { + var1 = var2 + 1; + } + } + } + + var5.setSelection(var1, var1); + var6 = var4; + break; + case 7: + var5.performEditorAction(1); + var6 = false; + break; + case 8: + var5.performEditorAction(2); + var6 = false; + break; + default: + var6 = var4; + } + + if (this.mKeyboardController.areSuggestionsEnabled() && var6) { + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + return; + } + } + + } + + public View onCreateInputView() { + this.mInputView = this.mKeyboardController.getView(); + this.mInputView.requestFocus(); + return this.mInputView; + } + + public void onDisplayCompletions(CompletionInfo[] var1) { + if (this.mKeyboardController.areSuggestionsEnabled()) { + this.mShouldClearSuggestions = false; + this.mHandler.removeMessages(123); + this.mSuggestionsFactory.onDisplayCompletions(var1); + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + } + + } + + @Override + public boolean onEvaluateFullscreenMode() { + return false; + } + + @SuppressLint("MissingSuperCall") + @Override + public boolean onEvaluateInputViewShown() { + return true; + } + + public void onFinishInputView(boolean var1) { + super.onFinishInputView(var1); + this.sendBroadcast(new Intent("com.google.android.athome.action.IME_CLOSE")); + this.mSuggestionsFactory.clearSuggestions(); + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + return this.isInputViewShown() && (var1.getSource() & 2097152) == 2097152 && this.mKeyboardController.onGenericMotionEvent(var1) ? true : + super.onGenericMotionEvent(var1); + } + + public void onHideIme() { + this.requestHideSelf(0); + } + + public void onInitializeInterface() { + this.mKeyboardController = new LeanbackKeyboardController(this, this.mInputListener); + this.mEnterSpaceBeforeCommitting = false; + this.mSuggestionsFactory = new LeanbackSuggestionsFactory(this, 10); + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + return this.isInputViewShown() && this.mKeyboardController.onKeyDown(var1, var2) ? true : super.onKeyDown(var1, var2); + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + return this.isInputViewShown() && this.mKeyboardController.onKeyUp(var1, var2) ? true : super.onKeyUp(var1, var2); + } + + public boolean onShowInputRequested(int var1, boolean var2) { + return true; + } + + public int onStartCommand(Intent var1, int var2, int var3) { + if (var1 != null) { + super.onStartCommand(var1, var2, var3); + if (var1.getBooleanExtra("restart", false)) { + Log.e("LeanbackImeService", "Service->onStartCommand: trying to restart service"); + LeanbackKeyboardController var4 = this.mKeyboardController; + if (var4 != null) { + var4.updateAddonKeyboard(); + } + } + } + + return 1; + } + + public void onStartInput(EditorInfo var1, boolean var2) { + super.onStartInput(var1, var2); + this.mEnterSpaceBeforeCommitting = false; + this.mSuggestionsFactory.onStartInput(var1); + this.mKeyboardController.onStartInput(var1); + } + + public void onStartInputView(EditorInfo var1, boolean var2) { + super.onStartInputView(var1, var2); + this.mKeyboardController.onStartInputView(); + this.sendBroadcast(new Intent("com.google.android.athome.action.IME_OPEN")); + if (this.mKeyboardController.areSuggestionsEnabled()) { + this.mSuggestionsFactory.createSuggestions(); + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + InputConnection var4 = this.getCurrentInputConnection(); + if (var4 != null) { + String var3 = this.getEditorText(var4); + var4.deleteSurroundingText(this.getCharLengthBeforeCursor(var4), this.getCharLengthAfterCursor(var4)); + var4.commitText(var3, 1); + } + } + + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java new file mode 100644 index 0000000..b05585b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java @@ -0,0 +1,77 @@ +// +// Decompiled by Procyon v0.5.30 +// + +package com.liskovsoft.inputchooser; + +import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.widget.Toast; +import android.os.Bundle; +import android.content.ComponentName; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ActivityInfo; +import android.content.Intent; +import android.app.Activity; + +public class LaunchAppActivity extends Activity +{ + @SuppressLint("WrongConstant") + private void addIntentFlags(final Intent intent) { + intent.addFlags(67108864); + intent.addFlags(536870912); + } + + @SuppressLint("WrongConstant") + private ActivityInfo getCurrentActivityInfo() { + try { + return this.getPackageManager().getActivityInfo(this.getComponentName(), 129); + } + catch (NameNotFoundException ex) { + ex.printStackTrace(); + this.makeLongToast(ex.getLocalizedMessage(), 10); + return null; + } + } + + private void launchApp() { + final Intent intent = this.makeIntent(this.getCurrentActivityInfo()); + this.addIntentFlags(intent); + this.startIntent(intent); + this.finish(); + } + + private Intent makeIntent(final ActivityInfo activityInfo) { + final Bundle metaData = activityInfo.metaData; + final Intent intent = new Intent(); + if (metaData.getString("intent") != null) { + intent.setAction(metaData.getString("intent")); + return intent; + } + intent.setComponent(new ComponentName(metaData.getString("package"), metaData.getString("class"))); + return intent; + } + + private void makeLongToast(final String s, int i) { + int n; + for (n = i / 2, i = 0; i < n; ++i) { + Toast.makeText(this, s, Toast.LENGTH_LONG).show(); + } + } + + private void startIntent(final Intent intent) { + try { + this.startActivity(intent); + } + catch (ActivityNotFoundException ex) { + ex.printStackTrace(); + this.makeLongToast(ex.getLocalizedMessage(), 10); + } + } + + protected void onCreate(final Bundle bundle) { + super.onCreate(bundle); + this.launchApp(); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java new file mode 100644 index 0000000..3b9b29a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java @@ -0,0 +1,77 @@ +package com.liskovsoft.inputchooser; +import android.app.*; +import android.content.*; +import android.content.res.*; +import android.net.*; +import android.util.*; +import android.view.inputmethod.*; + +import java.util.*; + +public class RestartServiceReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + sendMessageToService(context); + + } + + private void sendMessageToService(Context context) { + Log.e("RestartServiceReceiver", "Sending message to the service"); + Intent intent = new Intent(); + intent.setComponent(new ComponentName(getPackageName(context), "com.google.leanback.ime.LeanbackImeService")); + intent.putExtra("restart", true); + context.startService(intent); + } + + private void onStartCommand(Intent intent) { + if (intent.getBooleanExtra("restart", false)) { + System.out.println("Restarting service"); + } + } + + private void killThisPackageProcess(Context context) { + Log.e("RestartServiceReceiver", "Attempting to kill org.liskovsoft.androidtv.rukeyboard process"); + ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + activityManager.killBackgroundProcesses(getPackageName(context)); + } + + private void restartService(Context context) { + // START YOUR SERVICE HERE + Log.e("RestartServiceReceiver", "Restarting Service"); + //final Class serviceClass = classForName("com.google.leanback.ime.LeanbackImeService"); + //Intent serviceIntent = new Intent(context.getApplicationContext(), serviceClass); + Intent serviceIntent = new Intent(); + serviceIntent.setComponent(new ComponentName(getPackageName(context), "com.google.leanback.ime.LeanbackImeService")); + context.stopService(serviceIntent); + context.startService(serviceIntent); + } + + private Class classForName(String clazz) { + Class serviceClass; + try { + serviceClass = Class.forName(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return serviceClass; + } + + private void switchLocale(Context ctx) { + Log.e("RestartServiceReceiver", "Trying to switch locale back and forward"); + Locale savedLocale = Locale.getDefault(); + trySwitchLocale(ctx, new Locale("ru")); + trySwitchLocale(ctx, savedLocale); + } + + private void trySwitchLocale(Context ctx, Locale locale) { + Locale.setDefault(locale); + Configuration config = ctx.getResources().getConfiguration(); + config.locale = locale; + ctx.getResources().updateConfiguration(config, + ctx.getResources().getDisplayMetrics()); + } + + private String getPackageName(Context ctx) { + return ctx.getPackageName(); + } +} \ No newline at end of file diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java b/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java new file mode 100644 index 0000000..2a061b9 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java @@ -0,0 +1,55 @@ +package com.liskovsoft.keyboardaddons; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import com.anysoftkeyboard.keyboards.KeyboardAddOnAndBuilder; +import com.anysoftkeyboard.keyboards.KeyboardFactory; + +import java.util.ArrayList; +import java.util.List; + +public class KeyboardManager { + private final Keyboard mEnglishKeyboard; + private final Context mContext; + private final List mKeyboardBuilders; + private final List mAllKeyboards; + private final KeyboardFactory mKeyboardFactory; + private int mKeyboardIndex = 0; + + public KeyboardManager(Context ctx, int defaultKeyboard1) { + this(ctx, new Keyboard(ctx, defaultKeyboard1)); + } + + public KeyboardManager(Context ctx, Keyboard englishKeyboard) { + mContext = ctx; + mEnglishKeyboard = englishKeyboard; + mKeyboardFactory = new KeyboardFactory(); + + mKeyboardBuilders = mKeyboardFactory.getAllAvailableKeyboards(mContext); + mAllKeyboards = buildAllKeyboards(); + } + + private List buildAllKeyboards() { + List keyboards = new ArrayList<>(); + keyboards.add(mEnglishKeyboard); + if (!mKeyboardBuilders.isEmpty()) { + for (KeyboardAddOnAndBuilder builder : mKeyboardBuilders) { + keyboards.add(builder.createKeyboard()); + } + } + return keyboards; + } + + public Keyboard getNextKeyboard() { + ++mKeyboardIndex; + mKeyboardIndex = mKeyboardIndex < mAllKeyboards.size() ? mKeyboardIndex : 0; + + Keyboard kbd = mAllKeyboards.get(mKeyboardIndex); + if (kbd == null) { + throw new UnsupportedOperationException(String.format("Keyboard %s not initialized", mKeyboardIndex)); + } + + + return kbd; + } +} diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000000000000000000000000000000000000..a249644576f16340638e45fad58b4de9d3ec992a GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`ZTmyVUT!HjEih-BippanUjAXEBnUC-w#x>+tbA{#NzbR$p?j+6?j}?19E!B zSikR7^aU`fXPg4J8W!v18%vtpo4VSo9Oz{lKn0zMncMzA?LSx%SXL$0KHCvel z?cg^#7Ps)c$KfMIe3=qe6L>=sbf&jGU95g)i;I>4_gMzNeYs1NlKszJelHfK{^u~y zy(!EwttU0!ul89K_UL@*43EmF@K2#?Z(ehqTEBb4m#aHcSSBTUT~~_OprSQrU8c74 tcF~30AMf&ieEZ#K-=TJe${_oyc{#k?S(!~8n}Ob7@O1TaS?83{1OUzlu@(RT literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000000000000000000000000000000000000..185e137eeb73c60301264ea0642dcd52be02a8b0 GIT binary patch literal 975 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UH$0z*rLC6XFWwz8eMf4FS3ObvuCJ zUQ-g}7YvMt)-4>n|8X-jv26d%v%_M}(XFYRyW|>HHycr2^<|I4eJ@4eppVtUZ8 zkC$|J&UTsVB=P31bY{J{x2%1cAgg(H#DRsHwM*9SaxAd@>Lj0X+pFg#Eeff6qhCspcaK6UaDy7b4#*W>WfcE!!x;TbdoPHaW?(b~K z^Jb~YuSwIU@utoS{dRA4>ibRKbymOG`}4EKds_h~4$hVGAD4F*TmDY6mihnjIYSGB z0KcMzaE!qI$t*&X@7Pa{IAG8ca!1hSUWx~=V*5^=Y8DOQY+dzgR)TJ&jT%PfQMdGcku^jzOJzu%a6d){l_z^IX$RL#9)p^J^!SNnZOtaIIrT5qrR;{~P= N22WQ%mvv4FO#nH)WEubf literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..b046e647789c1719579c7c59b15eb78bcc26108d GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UH$0z^D-56XFWwz8eL2Lg1Urd|((Q zlmz(&1H+7km5D8&uD&seM_||gznfS8{`K>pMe84L=6Fx5uk-G{p6MljuiEm}_e9Ra z*=FB-gXWf)#FUrW#D-PwR^-2-_2Q$8!ILb#f=|vVwNIy|?OB^Dee>ey3lTaE&8|M$ z#~=RU2HG*x)5S5w;`G(Y*TW7Q@U;Dm=2^-kc}e1ufwyGp`c2>ORiFP}&;90v@8crA z>kG8q|0(^ITDSW5ceW*$zHXi7$Ec?)(_X6d&}2Su?@YDhMmL{{Ess?xV(eusG*3w$Wy_v1p;?PT>OqspQLjoS@H0fZyUGQAK;iA zBJo91%ZP=8hj+F^mzzP9(#niAf{#5PWENItQusz`M{i$8HzJfK0nzQ6UA^! zbw7uW+p0$%Ox}Sq*LL%)dD0jv85+>3vSHa0-8%uVHScatJ0Es(g50?_-!z>Chtv+O zeQROAT=>S#-sg&$`SMN=9!!Yk^sN+2sF`&zP`Kbs$W~TGzlfcSE*n3Hx!Af)Z|*Ce zQqfF<$%jlm`%Bhj?!Tq}F70pcLXo@6-uoP8KK1Iq(8LP8@BB8?kE`$dJb$XO^|Vks z)9sh?8tpvFxo1=@bWr<#a??jU!>JduvV4NxZQV38u#+j@c*z~v@Gt9Dd+lNQTl#;Z wzSXy%tj`~Q+|k3?#4J%UA`ZTmyVUT!HjEih-Bip{WQ{uUTQ@n_sPFTkKE&3!LI*wDXXJmQI+|k3?#4J%UA`ZTmyVUT!HjEih-Bip#NzbX%P0976nIzyRzGsP zJN^Iv5XR|~C685Xd@i1G%Wu-3-6|prFU=4-S!5{qG$7EcZR+n;t7dl;=|<*SuaS8b z%D#q4N?B_`=Al;(T~|9d>=Fnu|CA-x9RDdRa{g6sky%FfrPJLG@Jx>6-kR_zWKEv2 poz|k3?#4J%UA`Z9tQY?xB}^SgB#QyKR^k(ygkYV z=t=LAAirP+1}5g9U>5K1KYYIZ`}+T{m#4=Ucel@uAAd(MayCutnb6;Re|N{kjen9^ zx21DzO-Wmuz%M4sz|6|XWYt<%-%u^H{r{ieWh|u@QpYEx88R?1YI(XihFF|FdYRL| z*+7K-gY6Z+CDpT&*=4QgJhypfx%y19*1h}xnSW|*IvA#K(dB>UttTH(YcE_H@Z&r~ zL*j)g=~o(jHnW`8Hd(4&b!5SgtaTSBEptjP%v-wp!jug$ZmVZ3nzC|&qtw!_u%kUe zucemyT;=m!@a9!h&@wYqudC0d7=1lg(?d`ECZFQGEOf$9uy2uQ zxZW1)yZ7Aci?yf7Y%eeSy*F<;Z{DA|O*=NUH#6`S$loY8E&S25k=df>f*{Yz1v~k! zyp&*F&SA6A?-z4f`dmNb3zAl5M-P4uVJR@&6(Y&IIltha{{y$i_>xIMak0lAd;UD^ zmt0v~T`r&c>4U~z*C!{{|KIHY^l1O7?KNN4IVb;|c;j90=DYIsO>1fx_b?iSZ_E@i Q14ager>mdKI;Vst05pAwy#N3J literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000000000000000000000000000000000000..916bf9c9c1dd6c20df7aae6fbab71b944e9294b3 GIT binary patch literal 748 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1619762b079f1dcbcec734d38645940ece81eb GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UH$0z?c`{6XFWwz8eMf3IRpl{g;3t zUR4t07tFxG#LCFbqSY#+TEpv_H~TYp)fbMfdwIS!t=s;nA^QLAdbuB7oLq-;cC7h# zdFlQ?2N&u7o_}=Ht&eNPo7;MWbKfrC^?c5<`;7*MCVp0edK!1c9E%nBbbkJwdp*(F z#Y3t<-XU3k-a9wZj04v;EIdAy-P-@egg4<^Y zjn!j(!^$F^&Mb1|hW596+D%+dd%ypCe^GvUym@Phx%#u0F%dCQa*>gt*RL&hE98+jTzKrU;YQE8 zgSr9has$P$FX(Cslq*%6sG?GqE)BE1a%gD*9`rxj|y^Dxsq@R!+7Zobjqe^!&8i+i4S*tp1i zLh;N>_7k68{g`33FLL#F?OMa0>rICw>S9js?=;^rS)co{LWuw{RWNwE`njxgN@xNA Ds+3yR literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png new file mode 100644 index 0000000000000000000000000000000000000000..08517dd9f218313aeb9cc159421f35aba469ee3f GIT binary patch literal 6857 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

ii?s&`)T{^aknwY0 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 D4?hSU literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..400d9d762492fb8d46db60f61aa0defe85e62933 GIT binary patch literal 7217 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%0AA1>P4+x>=y22{2oVOL?n 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000000000000000000000000000000000000..bed174765af65976afcdcab4e159a5cabdf26ac3 GIT binary patch literal 4085 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 S_+AZkD1)b~pUXO@geCwVg|s>V literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000000000000000000000000000000000000..049aba69da163442666671cccd662e442672c2c1 GIT binary patch literal 717 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbQ|Pfl)cYC&U%VeK!j5hQQlz;_HCH zlvonv7YqzG8D%CG=H6W#th?pv`pmam%?K8$=(qT|c=C7OrvEFywm;h3^Et^YV%COB z&Xab=?+JOaX(I14&3%>ZxlaP-S4~^pb;a{T;KIn?D}J@C|51By+xs-TR#gLIr#2s< zyavOc{imCOmi_Q_aSYKozjwm*Af`Y8m+vuq62rq;mDSEH(&%W4JY)G>=HGwqIo%Hr zny=D%zBGGT_O9jX9RJokXs$?gp18vKuh7L~5zXA4I!c;z8|HUgeBHsuvns3gTUpnR zjVmQ%jILhN@`Z+HgzwB;we6;Z4#EXV&1a212IPp*|QOkE9!)4c< z&fC%^?Yt?J`f`qO_8Kfa9IHU0Yb^7?fyPk&e2-Sl^0%wDrC kBm3-iZzopr0ABgdDgXcg literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..bf41235fad9868c0f2456cdb1244ee738c3fbd14 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA{SWX7aRt)vMliT9xziUI1feBC ze!&b3jLb~zfZk3PJ6|LME}i{Jl$zbkbA{QcD|bmc+K$BT_Wo3_jf{C-EJxAwBq z`aBMvO*?K_h~Jgsi+1~F{^hL1#(IayrCYgfH9UK{yyaK~e@0u_>aJK7puuvUE{-7@ z=jTqm9(LG3z{PkY=UIU>9tkrqym4@9cX%P7@I~m=|9b98Ijogb-jV*Ymn8zZTnRF=nAIe(bRCruJ5@$geT^J+P>tYMH+ zkciT=9hkvDE1!=Cytld9Z4{cQcW`@hcFAiW$jou>HI`X|!$8ro* zv$S0|crHk=OIJAEU0l!gK`x-uz+#tN+(YJLoXyA5A4S>JC`M@q{&4#p&Tzu!Sd;~e z!BOXqBj=B-jJ>h;Q$bk!^U}z5zoiNj45#-T;k(#(w{FVcTgP@W#$9BYu`1})I-t!A Mp00i_>zopr05gk+nE(I) literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..3e09943f30fc2f4f54c8b39c7334c9cbfb6786b5 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA*#!86xB}^SrLwks60S0qJwXKi+{_$*Ce&X|Ho!LI=#kt3KcKkkCq`LW@jg4LBvQ^@uyxld?Pk*{T zchifzf3Mu=cmH|)z!#5p$Ie+}RlGx^F8H&2z!J$BbKO$;fZk;AboFyt=akR{0I0t% Aw*UYD literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000000000000000000000000000000000000..11a0928ad13418c765112c80d02d962376ce180a GIT binary patch literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAZ4dAXaRt)vh9Kzw^t&49blZ|3 zzhDLiCPrpW#h0&?-sitlcvDl$S|=~}qKc*Z`I8=A{$BA5f6wfey?0JbKr%$@j^%M> zuB3!0o{f9=9GJx|B|7cj=Kny0&U?BzhG?9hI`O$slYv0XO_>B%mko)BoA#C;{a&x< zUmzlKyik3z{O_lF(|c9^iYTy5cpz-An(&ulo=q&GJ`{5$2d%TIG*6H@!l5AUnU-Qgg>~Oxo z+;in)!J|*2muDtzzj~3m zM9Y(n6Q+~;U-^`fr!h@X-j#!MMPW+4?g{0e{q{Y@RXKxbzk{&PyF)LIl@>u7t8T8tT~j7(wtP`=>g6LR zR7=jLS$~Q>9Odliw))%Kg~{O`C9>DvIw^74ulC;jnQ=B84sAOw`QGs{{;)6Uo_yQG oe--=obWS~&`l4m&v;PkH*{j*Dk`Bns1Ui|))78&qol`;+0Qp?tJ^%m! literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e5c546bf679dc35ca0fd6e57477deefabde52c GIT binary patch literal 2215 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..f731ac333c31934b1192524516b8ee05c61005be GIT binary patch literal 642 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAeGBjjaRt)vMjlvTQ63BQc~D7^ zUoZm$GZUk!o)e4wDIdx6Z4EXj+}CaN*3M1Mo+-Qf@}t-1tO9oKI@T?q!o987#VBR{ z4v(dQXZP;w3p?}v+RI5&jt=_lN7W8hs7}nE%6T=Nqfi!*;*qOvT*>c$@iSwI%#Ji5p+%qX{ z^O>{yI?tv}^S))c=E&2lTe(7vO|LcgRw>_Eqvma=F1c-+;UT{byQljE9j)73Dj?_d zlY{HH`r6Bzi~nv{-(nqDa&hVHH;%jOZtmZ;&olPLiaU>F%D*4kdXR1J^{DKMiZ2_d zEkE|zTfcPghhJ*VIX9;0FAqt%#H%|y{@q{8dyLBt3995O+~5a#n!(f6&t;ucLK6V} CERr(- literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png new file mode 100644 index 0000000000000000000000000000000000000000..87011c930199ba1b5618d0d616481cd9928fe016 GIT binary patch literal 4181 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..60f7a0cb112e4e22b5cab41e47f0aeb8204a1d31 GIT binary patch literal 4271 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 R%_sl>002ovPDHLkV1h5dYheHY literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8de62838419c8c788e4ce51772f8c291a75e44 GIT binary patch literal 4177 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1a17e00379e013f71c2cb35e45cc6b9cb85700bd GIT binary patch literal 345 zcmV-f0jBRpj_kSl64iNsnK;0J|0Y=Q`=2nu%sI2{M>%O%@xIZL9K1EUN z$8kJ2O>^n{{yq#tUf1ipk>cU~`LxtS8#Gy=~SLEGjEDH71*C rn@!)rrUPTsi&}K$xsR@#BAR#s?i5tc)snr@00000NkvXXu0mjfH1&|) literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4ba3670631ae43fa8cba517ee42ae65f33fbd9 GIT binary patch literal 399 zcmV;A0dW3_P)&I=e_>6-2M5*po_A+Q)n9-R>GZ_qhE7N!_X?8UYue+<~3ew5R52?G6# z1-356*hf(mtE#HLnx;9BzU#Wal5V6Q+~Ya^$2g2P_7Y@lLB>PUg_KGN<3r5Xg2?my zvu)dxyE2r{!HY|aPb zcK`^VCVub{HaP{lPAKE7!m0U(;KwkvsBi*-z@iWbbamkY?GAwmXBnUu)*T^uD7FrP zbw>!E3sMIo@g)R+&|_SJNBB)eOwzN0M5waLB%_LMrXEX$U<&WicU9hf)0 t=##E|&vQ*Q_znM!cvrqx?aG-$1%IgTKhxo}W}W~5002ovPDHLkV1jqFvRnWF literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f7ae6f23039d81276865dffed5c3552cf6ec2 GIT binary patch literal 2825 zcmeH}`9BkmAIFC|HZ8}PJ4bGEw5^)^K4%tlghb_-vkx77m?IRCNUkA?IdYSb+)8X~ zW<%PfEK<4Y6h3_)kIx_R{o(U`Jl^lu)~4bG)j4^6O_R=@t* zJ=puc@#!6NwDnGyl@=5rgb01KKKCriQU-YJQs4foEE8_c_@8gfKOoX-TI)n&ck&z5b(bTeo`Rofyf?p% z=bJj*^ifTJ`p4wBbR{J1>;rH4zush@&|yUi*BV;Sn$h(nPrbaXkRahVVqBWZ_>4Mz zeLnADf7jpsG&?v+$(M8Vo^9_+yMz1l!JKQc^^V1dT)Lgh-GWc1r+D4BubH_;5npJo zNKT-1H3IY6PuPvR(wzIomNAD5N5eQVu8fDFB4MkZu{XvLcFN>oY zfAo0~_AeB*l?oGr{MwAEky4Zy?lDj5a?$IqJR_HLd&~hC^-zS4g&y!n1-|h#SEEF-+Vt%c zBWDzXi8#YYGr`}B=-23VDIIZ%{;~RFFefN34Bs-tqk?p+i{!ToyE@397 zdqB4j@;APZ+o(OF(xjjyDU$C~+d4}-stFWeu)-nB4H$n_^w!zhj0Byp{pcJ7^;0ky zi5aR-O8K0#VA;$Ci?U4r-a)FQryd)0rZmQA4~p#Uu=$E4B|r2IU**bIr3mS$i)N5T zM;;t5-{z8uH9y3f;k``M!|Y16%ayBiA?I1^fUML|w(_(0)^#B1?;CQbjwzmT-N&}4 zgbA9qed+S{ua_zhpXx4@2>WC zOZZ)qB(YmGVJjf+$2Srq@fu$fA&}vB40H~V zo%gd`Us2(Sx+KH zLYe4-5EzuXz7Q1R_}^;zxmyKCLMuXKZ^vZkyYj`n`%M0SdW)PXnz|`?C){h7&ZVa*m(=?zn?6 z&7p^>1Zgx`u%+#c$rrs>?lO!tStVJefj7g#`A}58StWKF6QTQZiI*hWF1A>%)PqLD zF($>UV125PI2f0KU&GqT?57tjN%A^*RlK>Z_5}j!sB`FEORGX;ijl(?)bUNKGu!sr zb>mZ8S5gtC-xwqlV)l|+!%5~4YUp>cuS(_5y8aOM?Fdbx$DaFm;0~&t$zMD&z zVDUl91~y?NtH98SL-hlXVl7<@_hqXJilyZzBr_&mCVr*9xO&g&O&{~O0{BJdjQ}Yk zG7|H=28BxRycJQ=-T?XhsjfNajJq-~sT(%ivS;wp zjFh<28afkB@ONBU1-&JJJ<^;{FC6Kd@YTbc4juHZu{o_l?@xw)} zT{_V%!blap;qD8u@yk#0ip>f;Hv0;8DY8cMXHH!*6fDNaM^5#oVAn6IwoMb`s$NC~ z9`B?aJQ%uV6KHTUQ7`sJgxs*Ue@}6&6{xwdh4hlXH#xNe* zh^NY5JAOD=>jds`w1*NWr6J)4WNu^bncej*-kp_ z2tE|pjXWjQ98>UZ*@Y(amqPg_^@WOY#aXditNbiUJ{a=~kR>$CidHXIe7G@jrjZcq zm0;qTPu*z!KE8;%av)_J7clQ4VM)^;n literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000000000000000000000000000000000000..fac12401f90e1142142ceda652dc318acebd5898 GIT binary patch literal 2160 zcmeHI`9Ir<77d|@rKnO=mz3I#gjCboeUnK@2sI4KAWwwa8dbDn8&qhuYGfq#+L>Zd zOR0T{C1Z(t)mmGzR;$9)?s@Z9ydU2A+PyNem+qVY&B#lv955j>hQwpobc}g z=q1@!O3VvP%BGU{ImiHwd2Lng-GcGqQu*x<#Jc>Z!bZ4aucSa!l1 zLjQ_R|Bug7t`o!` zxEoZ!UfJ!VpdtcB3roiOWxlJcU+I~sFZTC4x^J>&kz1nG+3B{JHTnHw%W&tHAqG{NZ>VNY zF%UIMbyY)2rd1E&*z`L8jl?*ieTxXB1nLoy0QS|r(M|J=P=-N5beZR91yPv~z5sav zy}hcsZSh~Pdg%%yku=U--v?+uur28eK0hr>2@^A4c*@%rTYN?%BbG@vy%KJcCdgrt z7K042dMd|f@gH=Mxx&#a0QS8? zJLos4BrjRrOswP*&l2*CVVtoNko0dxyjHpmpVTmJFLp5(tnoJ<>zov6iq0pua62yN zeOi7;9!!&qe#Qa+n#;8^?Jvt8t_>5wAmJOwj9LD=7WikX<~rEZO>z6s5ImkGFEh_a zF*fMj6cubP-Ynu{{0>>RUHfZrhSknT$qv6ib^11;^y}1uc93~ZrJiBD9KI-p*UAo_ zOW-kw1B>{BfcyzE8Ql1>LZQM8)~ZiLyiF|(nS0#j99eHWmr@!z7^)G){AI1mI|_Wh z=0)>E{zkz2BAQIIVXIIN-^#VCBVFIPB)9nLtgZ;TfC8LGM*YvtIqDIp-m4AmFov)8 zQjOsqQpI{38zdQ)wW2l4oLoK17MaT`cdM_3%^-+xr$Tom8cUK0WR0FSQu(G)wvS}= z_p^vLIkVu#=iEYQY_Z+S1y|{~xkr$`aFe8b_1Q?40blG~(@(V)GVBzWK?d;!Ic3^m z<3J>C-fp4v&FdYi<9YVzCf(#$Q!$#olE!dX4YLdCb865*#uMsO&UMz?H&csLFF;=M437?a5fJ)EsO==Zr6F7A$tu>tkZtOt z!W9?BtYnv;l0vvx=}$v_Ut8%bqQ(zu0iGSe9@FqnOJqi+lOttpc|;m1Igx2#kDwip zX)Upd5rmU80I^2U{F|x3V~jI?;aS{3s$41<2kjdLl0*SZ6aa|=1{#5+O?zjpMExO2 zpD5_q5V{bNiYZV}ulIoB=K$wD+U=1%%5(yYs}6OXFTL%jOu?tKiil80R+*TcX|DO;9nmcYsSdRinII71Ft(fP=i96Rv-hIqENMMLgWr zexg#tpD|$Wt+Ccrrsd-OS*(G}fYEX9`}}sD;g~dT!-jL~fL@o{1&>#T8gMW&lE&HG z%8(!E7yXScG~_NJA$8>Wlrb+e8s88P)r&a@zD?f`Cqc$;;poH4Bw1tqZ!c^5a0{^K z5h!XW&Pxb&)YROC>y39eHt*=csUA%%nv~Wkc{|KKdFLY^-mT@y=?z}(yjm-^5}3KF z`KLI|sh_F6U9*iK!N$tDxdem;Y%SdT?sTLVW3me@x{s%~3|%uvChrx>28WYWc_N~F z`~0H3p@1>@w0*l;hJIH&RS#9mKqkcB>nyBFW|G}AGL&@Zk1TzI=pIDh2fZWXC`OY( z4}oIf^YfUts;azO;o!|v|MDSzIwNgieRAxapMF~I#AJQzAlz###^J@$kIp&9?Fn_h zj%-WaaF0s-h6%L^{Dn}tbM(_fMe9sXK(B%SzujgV+iEM6cf{|7?DbJ+)~;QZeqPZr sy}Uaf5zIO0=PU<=V23*X0pI0gkSogVd>s`Mc=Db=Sd=wTY3?5TAGKb>(EtDd literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png new file mode 100644 index 0000000000000000000000000000000000000000..2786486abf3149c205cb4d898f1f99411c346acb GIT binary patch literal 20565 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb3597248c9a4d9b5d8928c9bb2e636925b539d GIT binary patch literal 1390 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBgK_VEh{36XFWwz8eK2EClB2)$;%& zetAidUoZm$6Eh>PPn@*O?f-!+H~s~%if{*sMsL6W;{04KFN>WI_hh-Ox%d3y0>1Sh z-M%bm?|$?9gYTRN9>PXPPu(>T*Gx&SP>|4lsUmqW-13|8t;O@%gs!&ooNQ4R@VEMV zncum-wl|n7GXF3~(X~Hebw?VenCiQ~`xP2haNN(PvE=*4RabU*X;|OfU~V`&zDY?e z>1nm>>L)#)AKNZFyK9=1*}4m-ci1gC)8?Js^B3sUMo$;V5Rc<;r~HiO4U{;3zWUjN zd%ttK-5#qodG(@hk=zwCJQSC15=+$-*`)9Jt@iA%a;x-dM-DIkZBw`Be)_iW zb3V`foM+r~RP_2Em!2JJPjuNFwAdUruyTw4e3HBG(HVIMrpw6;HQmjf|5-v-#8fZ{ z&0Od+%h-Lt?)4o!q2exl2Y55Ho-Ap|em93*P&2vH-qZGmgHhlK|8qNbwOo<;&SAP_ z>5^qj_0OJ|GFw|A`{t(t3+v-?{!?a8j}O?P6fDT6up&b>K!Bmk+arA+Lr`OZ=7v*G znm!vPlt!^+pE}9w!V%bbYU{qz(7-kNldN+XjLgFFwYnFy@vLGs%U`+ryo0lHrk~Qv z%C$pqymHHwEvkG}y*TDpw@!R0FW+!! z0s9Fr;mh2GO>>V-IN1^`!2iIc=fuh3`Aeo_w@&JIaKA9=Yc#uTvh`I1ch6)lEAx)3SCY9IQyisBJ_a#WI*4^eUy;`H zzhSuLQP4w`=k58-`g;4!h40JepG_~{kh*hb0hi~lmu1_dmP?x~-@7;8 zW!^5uy&}65V`l&R!)R%%SNBu$?8cL38vjoBy|#bN&iXvscJWvJC&2u};OXk;vd$@? F2>{RwZL0tP literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..9a55177fde0e3c1abeb24cb95e03aef5cb6468ef GIT binary patch literal 1113 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBgK_U^@+y z7M2A01p|YbnTdmo&2;J0uV3yI2Y&uv_VM?>zgPKWZvAavb;c! z+QC;U*E~{wb=4g~bz*_m3y>FfcH=c)B=-cpQHl8tHu5LEzjQJ+_4*Y`eTw z!;}=nME0x-3E86?vQnd~qoSc)^x=Pd%h`)>sz}S#KB|7<{QY-X+J4JN7RP_^GbEHT zSWHN_KCqC(DSOlQW`-BmiLKHrHZd{&RcB}OZnm7_<(xW?mvPGZm5Dk>IOeAR{JS_} zVpxJ}Z)jucCrjJDxQls_PJlzc(d??T_}jF4qj#-E;)6R?EMrD78~g>qtH|y)&IFB{p)T`c44Q~)ZCed_a?6XXBISf`h!~Q@b-k@HzrEys^@AH{vEem zQ{9;KIe6Qphab+=_vKAG8`^N!_O3Tq>07g==9=wwD^Ks(U0&9HDDUfv=>2t*FPZVn z{_y)OlV-hj?wP~XLJd1{yW{kpWzFJsRbP0l+XkKalxk8 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7b4a1f08708fd56f97b9a8c4e44ef57a1c244a GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-segQrqu0Z-7^}ue!HBCShrAmVQ zf*BYXnNn1k+ukbI{C})i|L>7f-S39Ke;Qj`(nL>R1FG8Z>EamTas2Jn(?W+7cw9Mu z__eND`s)Ax6*Ds&%fI*CPTO_HlF`eFBm0l2&=EVOcon0DrS6^<4}Q+h^{Dvg_pM!- zE23`FWWIEPg8FMlaRvwehVOmZ!1!+dmU9XpKAvBibl{PG7vtIW5*PkTJoxvfj-%lJ z-?!|I@7fun&u{r*=pc8NpUGb+VY*BHEW5B@$Nyea(2GB&f4{u9%RFGqax)aSZV|{&uRP*dYZT7vV)= zxgl$GtNu^s+L9%%mlk2(|F&MX<$9ha4SQTu`Y&58pS5Bs=$3cs*?%VM)R)W=$I<0E6jad8d4CE@lAC2Jav zIx-xc_-S3i0*iTGjLrKc7Rb%MajB;A$HJT2nhsyIk$UrC<6~aFxZlF6&I-qUl9Jl^ qjI6Xy8Z3Lf@1e8e{xxg=U6D475}5YxlVu6e3k;sFelF{r5}E)dJhv0PcF%25o$6%XR(j*pU1uY`)Up{&-Zi5ELhzl-(XmE zl>qv&JtDpBmIKCv0s$i>Bs9E90JX3*SI+! zt`DqL33Lk#KXhN@E9=Dr%O*~E{dH-4%8Xq?|E?|CcP1l~^Jf40ADtWp!Ok4@zoTns zUOcCoV()lL-^i+xrGY&*&VKFz<6R~*8Dmzh4{cu-+`t{3xL%%*?eD6n`D|>7`^EE` zXM0MxSqF;nmVcj8_Kp9CgfGwCH&V~6Vi@e?blVl5>pzy-n9OQnnHqHK{o&$sAqgQr z{{LaHkUuZlr=Qc{of^Hqi}9Jub?>_dtBv`;%$~`<;bM^7jk%Lmnd)LU3ae*coN;gc zL{5*q%SM8pMdkK0{X%@z`8T_8pLukA`SUGtagUEM>`84XXLiu7O^NztvgOG2@T`kV hw{QK@{agAM)A}A?-gOr~aRMWh!PC{xWt~$(69D_TlxY9} literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000000000000000000000000000000000000..16fa7d6ec0718d7f9021f0474ab79ba7e7c929c1 GIT binary patch literal 901 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBgK_U{nt932_B--;DyiA@KH__&Q)P zC6)yF1p|YPsi~Q{_J3XV?;qhcm46bde#TcMCY2?p{4M`g@-H>5v?!O8or8s~`1a#R z_nuDHy7S;+_P6hA46~FJlCI^Z3%IS;D`&~|6>Uj}x_!6J?+9U5yZ#t}Jz2(op z`HNDI8tO-+&06--oV$LH&G|RGH>^?kbG?C~WXA(Wj)mrR_l}(Ho-Ll3A>rcJ{7I-s zp|9yks=>YHIQ^;}Jl)@a{eZDdDnLzq4 zsW&feel5CitMq_K$FE<~&;D?)IHou;=E_=Kzey`om>#X#mQr?8w_{8FlrZN0Nzq4Njh9DG9r&xrIlW@(^vs~A**czm z|NAxXMEHtWvVQ%x&Z#)QGC;nR?;+>y$}+Kzgpl~SwITO8uFqs|j!(01eDT)fK)K(p z84t|)e%;&gRMvmfyBpk3_J+Opd3!kQg1z+Cb0)8zxU*i@-5Iy#_4QZ34*zYfVO;jj WIXQN3WIQmTFnGH9xvXZVb$eD>Xc|NCF3_)VGOnj~*~ zkJ(1$`)uozik*v`MO_@u%hfR`z7T%5z3eyJZ`C<-ns`)BtWRjEVAJYI_-}d4u&O@D=bOc(hT8hLD}_xU=}MbbHTWBsYGGd&7-O)hxtJ7>OeS?SS#H@EFO zJ>#p;y7@;w3orb*sXgKUAOG6vhVNBk>~}qU-s39HxF8|(wYhN4u}Nyt4NHFQjk-|6 zJfR>pZ05<&I^SMjcF5Z_btl_RFO5^PH|ccWwq8H?H@mf)aBI%`#Kp&TCp-wY<&j-} zW|h!+xeuST=2ZRX7v1~Q!L*|8ALE|&=0>mk1s+_!eQkZBgUi$Mweo@`&mI4#l`$?_ mwN&BFygSMdRxIZ8+~0pf^5>#gnhSxU&fw|l=d#Wzp$P!AF;1EQ literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e5c546bf679dc35ca0fd6e57477deefabde52c GIT binary patch literal 2215 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..076e2cab81132ec4d7bbe59f8e7363ceae6d5759 GIT binary patch literal 1187 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png new file mode 100644 index 0000000000000000000000000000000000000000..c0752eaf25a2a76566d92549f753eab6e3c4d19c GIT binary patch literal 8982 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png new file mode 100644 index 0000000000000000000000000000000000000000..921f00b4caf8c9fcdf06094b81bce716536b1b7b GIT binary patch literal 670 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 It%d{gKTniYBLDyZ literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000000000000000000000000000000000000..16d4a7694778f3ee508fe3387d5a65b2bbdd0ed6 GIT binary patch literal 5637 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 Csm$#F literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000000000000000000000000000000000000..16f71a0a9e3afd921e0010c01a025cec80547c6e GIT binary patch literal 548 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~aez;V>;M1%-;oForB2)l)Gk;O zlUP{Bm9IaKLi@f_i45ixzL#CcP3-?KZr~ z?s<0emJG);OBUp=(L8YJl(l=|q^Y7IhxvXMWrvknhBY&Z%WdQG3cPXopXIA$E43+N z!CU_w>dc?(akfu%71ycOQzy>%8hx49DnFB3 z16#=}K}n&-yG)ntmNbgEB09lp?ySRm3$tzuCVm&}{QWY;(?Z_fT<-bil=O}((zi;? zBNm^%>Ry+2SGPMyo|s;_5g_P@**1pda^r^*S6Wtw;C0^^Or)78&qol`;+0CW}& AEC2ui literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..2704ab0a3aa64fa94510200fe4b5042c568dc9e5 GIT binary patch literal 1280 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`ol6-Q7|#Xxgt-3y|Nq@67%m}j@%>{{ zU`0oGTC$nVD9=y^2dW%)+u@}dQ^2P0C-hZ?{2;F}6lX)-K&WB3A zlUjEurZ#WCui*K^?q59nwtMo1Z(k*{ZMiFFbSY`m9b?9IDH~)=-Z!P)ln%=fv&gc_ z`FJ2s`^t)b(PbBw@ShiPC|aa`PS~kpfyY^ws(EJw-D;TT&Oa^S{;h8IvG0xC;*XSf zt7doaPygNfl*O^7BlDzwi6#RBle?#jV@Sl|je-a4>nQS&2* z*aQVFK{F5jt1`6(tBf2tB%~T$dVDz~Q@UPd85eI@(W>fm;FOe3LbCS(1!e0k4O2I= zcUxOH?eII7u&!H|asA3&Nn#>-0+a7J%JAzM9G^VZA)&f5*;6&mPGE9Pnccvu@Og?llq(x@ol-%ac8pho&Pv&b_Iz_6; z_y?wm@tUi*CB$_fovwfOk;1bh#|6%9$@{_M#nGO2#6_v7UMR0PX7a;GtJV#Ur?PgO z@$_|L*;XX=@RLR?<8DjInF-39HYut-70|m;v{9_L@@E1w*N>ZAifW>QdWzi}MXsrS zN_e|;K}Z;z;mSD$oHKh9{ajTe7I96AT77WNtf)h(DYCOHzw#zLJf^0xU-Hb?EX9lo z&e~Z{TNt~=wjXr8b&yH6i>3RP-<3I+uJ*=lXyS^$DyPI%FMef@_MPsqE6l5zLq!+8 zGTm~iWikI+spQx59a4OyPq@vUC05<(9j46WE$fiBnQd{H(shL|RYLY0N3OSBQ9ih! zvm$zHl*?@6lN{?EBe+A?Bp5Za?GC>ovgpp`^*#p_+>WihJY)I!1N>joo1!ci|6Lzw ez}2S4z_2i=;n$qXnNh%m&*16m=d#Wzp$PyX`vubg literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..395a6407eb9bdd86595ba23392fb6db28b5172cf GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~bbwEY>;M1%-{Amr?-_&wm2s8? z`2{mDDA%?BuWx9a@4f32P;jfKi(^Q{;kVZv`3@`axLjN!a^|o9on?VS8f$Fgy+SMb zKA0s)=zFUO{cK)y*+xObR#GFC&1+`kJwB#fKE_%ZhFh|-S~DB@+*|$JTlCzUrQDmM z+#9Xj8>)`;N2V>vKhDkDCoCl^A7adWR)*iC)Mi59g)9-1sqbv--Z>R1AL4zhz{x*5 zFhJk4zkcb0Fd_RBUXIgMrgJP_;LOw&ws1kL)2WPw3TxzwTjxeA#(4Vz9ddIIL(^QI Vy??(790GcP!PC{xWt~$(695RZbwvOG literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a750f2e1b9d222e4cc1f6f21a5fa0bd9b2b99cbf GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~T!2rA>;M1%-{A)Q8UoHhm0Tr3 ze!&dN4DEIQ>l+%IdebaH#%%C(aSVw#{PubzU$X&^YoLZo*|+_PQKx+BI^MUp{t+u! z;g=icLP>JL=9EUiNt{+* zW-k;Z7shUKx?OAJygH>_jI-gN5aU@ProSp|zq|yd7_o|SXkEC(wdE$ORb8vz5+{F` zh3>1*aY=_}v1Hwb**Z=?jzZ(cpd;9>Y=!aUry+>_@wKlimM||#WQa@<#6#7 z-%WORg-pk7&B>|O(ntLG9^8mj2s>!mzO?-iUqE7}V%SmFP;=QQZgXb1w8Zdl+%wBA z#JixO%2_uQboHTQG%V!VSi#T&pX2jzQ`RoE+H~cxY+7a0;9rtgx=6K`z{R z54(V>=8@kDSDzPkykclx$RowauuG-U?QuZ=#?Af@-+t_{a#J(Rn3X3ddBUNb)pozj z?9|^IIgR5}FUM!=y%)QX^mCWcmv8HsYu6m9edQ1@^y0VLgwjF;zapIg4tem4W8f^JA&mk9n9^81>2=vA>lSHeUuM_l|~ zYVxDq=7I>nQrGEDb*$dA|b{tLFbh-bBnEmJbdR(fLo_}<9 z;AB;p*}=E+1&fE%gjq};%Na#XrIrM$7dHlZ^)h8fz7m+UaFUo@l(prqDbAX!TU)p6 zc`H!V8MIG;Pb*+~#(L4cLh2zJmarX zaMR$`l{=ASC7qe`wtbDEmGYq%r>4!AV0zHdEVOQS*_`_1{E)nl6FQH!T|2kUn!RNI zX7;pVF_Za>`Yl4tQ981=6${*-zkAwL-eXg_;CaDk52ybkv-agw_cHCYGTTyB{9W#C zx30m@l{(hF^JfdJ|MGA_|4z9nA2pe3Uml#Pbith2sEzopr0A>Q1hyVZp literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png new file mode 100644 index 0000000000000000000000000000000000000000..f128b137506d44ba4193400a187d5cfe1141f3ab GIT binary patch literal 465 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAjKMSxF;>;M1%-wgzK=pDTR=mw3F zAirP+26+X!d-w1Dm%Sr%`!%ytFRHTb`~Cmd zEjH)BcSJwAJwB+fkk8b!=i7w;1z9TUC-^_)JlfuT`^1g_2}jLYyPb=BnRTYEyHLcKTlk1g)i7ZyN5Ed@HP=cq_BJi_^=iGu z>o74VVAjDPHO&cItPX_TQryYfF-dTerN`4tJb|(a+sz$+%Q{Tw2?z|c(%63d)h?YC z`A?QOJWu=Iq1a}{8oTcP-4g=G3uTTM%D&}ew^5s4^z6SV(60=hu6{1-oD!M<4+8v@ literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e5c546bf679dc35ca0fd6e57477deefabde52c GIT binary patch literal 2215 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..e8315a5c8ace35f9f5785fce8088772b9c8eac93 GIT binary patch literal 493 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a06cf66cd64a1a7822bd7484adf40392eab731 GIT binary patch literal 1244 zcmV<21S9*2P)l5UdS zjo0Jde33>8BJtYaXOHcfv8Pfg_A4~Pmvv;raOnR2{#_6RlOnSsi#InnTi4guhw}bY zPTqIq{TBeXAt=j~iL%E% zA|%uX{aK^Ym=IuA(UWZfo`8@#o{49p4g*JksD)wai31CtRxSFrP#}KiNIv1V*X~Y|m<)Q!|qn?)0hQbjb6WbH%Mbkd$ zcuRA{G}=@M0;JM!F@%c_e4JWB+q@>=?(XhW8G3!U(SSC3M*xN=HaTSlK1QmRR8nXI z{2{5(+5myyk~IsPxc&&ZxVU&JJ%4uq!EdRgU0|R>z~$xTE2+T2pn@ME;Q&}TCE)!0 z`~|^(Oad@*LICt0%)SKvKN9vb%n4!Y)s8%Doq%_g^^~ilbOZ>?-w1NgkuaAF0dQmx z;KaM;ajOJ08jaV+?MHx<5pV>|tPo%fYc&F!nuJ<{`JgueQrKq#npwW|MF2J!t>^MujN z5!2j=(G;OMxyr%03{UqUJe>zLln-d|h?4RVCGLtnIHYGr!6pH--upLsP`fAxwdtf$zN)~O zs7DB%;$$uDk2<_LnD$>3)1>SB7$QhB?0dAEY(dwDmnukWh&^S{2n zErA0AdjElckTI1>-ziM?3I6rI^Asi#b@Utwlm~dNzXc4m(^x$nL}xzQY@*wk$V~zK zId2lVwP)E7IWDovIZlxsLnoQ1$+l4|A^6(~vtoFY&UU6m>1<~Tlg>8ApRiKoiMEgl zonwd?Es{yQG%3yFqAh$5c|qN9eWyc6+!7E8{u-8sF8DA<7#+WWIw?S*LqOE92iv*P zwRMM}*YQj|+d@v@;u6qC#*jHAQbnF#n`hQgmdSfu#WN!D`RdKC`1%4!h^54FPMoO~ zS;GDTR#@D#5LI7BjU1W(;X6zC4StI + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml new file mode 100644 index 0000000..dcae5d2 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml new file mode 100644 index 0000000..dcae5d2 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml new file mode 100644 index 0000000..f583d88 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/layout/candidate.xml b/leankeykeyboard/src/main/res/layout/candidate.xml new file mode 100644 index 0000000..c65ffd8 --- /dev/null +++ b/leankeykeyboard/src/main/res/layout/candidate.xml @@ -0,0 +1,5 @@ + + +