From 79c310d9b14c65aa6cf2ff87707daeb8ee078340 Mon Sep 17 00:00:00 2001 From: Yuriy Liskov Date: Thu, 12 Feb 2026 06:30:30 +0200 Subject: [PATCH] Initial commit (recreated history, MIT license) --- .gitignore | 24 + LICENSE | 22 + README.md | 66 + build.gradle | 78 + gradle.properties | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++ gradlew.bat | 90 + img/leankeykeyboard_logo_small.png | Bin 0 -> 5687 bytes img/leankeykeyboard_screenshot_01.png | Bin 0 -> 63663 bytes img/leankeykeyboard_screenshot_02.png | Bin 0 -> 60368 bytes img/leankeykeyboard_screenshot_03.png | Bin 0 -> 69928 bytes img/screen4.png | Bin 0 -> 45069 bytes img/screen5.png | Bin 0 -> 39653 bytes leankeykeyboard/.gitignore | 3 + leankeykeyboard/build.gradle | 86 + leankeykeyboard/proguard-rules.pro | 25 + .../ExampleInstrumentedTest.java | 26 + leankeykeyboard/src/main/AndroidManifest.xml | 130 ++ .../activity/PermissionsActivity.java | 43 + .../activity/settings/KbLayoutActivity.java | 26 + .../activity/settings/KbSettingsActivity.java | 26 + .../settings/KbSettingsActivity2.java | 4 + .../addons/keyboards/KeyboardBuilder.java | 10 + .../addons/keyboards/KeyboardFactory.java | 10 + .../addons/keyboards/KeyboardInfo.java | 12 + .../addons/keyboards/KeyboardManager.java | 103 ++ .../keyboards/KeyboardStateManager.java | 25 + .../keyboards/extkeyboards/addons/AddOn.java | 48 + .../extkeyboards/addons/AddOnImpl.java | 131 ++ .../extkeyboards/addons/AddOnsFactory.java | 355 ++++ .../extkeyboards/addons/Support.java | 64 + .../keyboards/ApkKeyboardAddOnAndBuilder.java | 95 + .../keyboards/ApkLangKeyboardFactory.java | 145 ++ .../extkeyboards/utils/log/BuildConfig.java | 6 + .../utils/log/LogCatLogProvider.java | 48 + .../extkeyboards/utils/log/LogProvider.java | 34 + .../extkeyboards/utils/log/Logger.java | 228 +++ .../utils/log/NullLogProvider.java | 39 + .../extkeyboards/utils/xml/XmlUtils.java | 825 +++++++++ .../extkeyboards/utils/xml/XmlWriter.java | 252 +++ .../keyboards/intkeyboards/CheckedSource.java | 14 + .../intkeyboards/KeyboardInfoAdapter.java | 62 + .../intkeyboards/ResKeyboardFactory.java | 139 ++ .../intkeyboards/ResKeyboardInfo.java | 111 ++ .../addons/resize/KeyboardWrapper.java | 99 + .../ResizeableLeanbackKeyboardView.java | 76 + .../addons/theme/ThemeManager.java | 159 ++ .../addons/voice/ActivityListener.java | 7 + .../addons/voice/RecognizerCallback.java | 5 + .../voice/RecognizerIntentActivity.java | 29 + .../addons/voice/RecognizerIntentWrapper.java | 26 + .../addons/voice/SearchCallback.java | 5 + .../addons/voice/SystemVoiceDialog.java | 45 + .../addons/voice/VoiceDialog.java | 5 + .../addons/voice/VoiceOverlayDialog.java | 67 + .../addons/voice/VoiceSearchBridge.java | 43 + .../settings/BaseSettingsFragment.java | 190 ++ .../fragments/settings/KbLayoutFragment.java | 43 + .../settings/KbSettingsFragment.java | 53 + .../fragments/settings/KbThemeFragment.java | 51 + .../fragments/settings/MiscFragment.java | 53 + .../leankeyboard/helpers/AppInfoHelpers.java | 156 ++ .../leankeyboard/helpers/Helpers.java | 251 +++ .../leankeyboard/helpers/MessageHelpers.java | 90 + .../helpers/PermissionHelpers.java | 93 + .../leankeyboard/ime/EventLogTags.java | 16 + .../leankeyboard/ime/KeyMapperImeService.java | 107 ++ .../leankeyboard/ime/LeanbackImeService.java | 403 +++++ .../ime/LeanbackKeyboardContainer.java | 1587 +++++++++++++++++ .../ime/LeanbackKeyboardController.java | 955 ++++++++++ .../ime/LeanbackKeyboardView.java | 639 +++++++ .../leankeyboard/ime/LeanbackLocales.java | 92 + .../ime/LeanbackSuggestionsFactory.java | 96 + .../leankeyboard/ime/LeanbackUtils.java | 198 ++ .../ime/pano/util/TouchNavMotionTracker.java | 164 ++ .../ime/pano/util/TouchNavSpaceTracker.java | 599 +++++++ .../ime/voice/BitmapSoundLevelView.java | 191 ++ .../ime/voice/RecognizerView.java | 214 +++ .../ime/voice/SpeechLevelSource.java | 25 + .../receiver/RestartServiceReceiver.java | 28 + .../receiver/TextUpdateReceiver.java | 15 + .../leankeyboard/utils/LangUpdater.java | 124 ++ .../utils/LeanKeyPreferences.java | 119 ++ .../leankeyboard/utils/LocaleScript.java | 727 ++++++++ .../leankeyboard/utils/LocaleUtility.java | 57 + .../leankeyboard/utils/TextDrawable.java | 510 ++++++ .../leankeyboard/widgets/DialogTitle.java | 64 + .../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_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 .../drawable-nodpi-v4/ic_ime_accent_close.png | Bin 0 -> 439 bytes .../res/drawable-nodpi-v4/ic_ime_alphabet.png | Bin 0 -> 548 bytes .../drawable-nodpi-v4/ic_ime_clipboard.png | Bin 0 -> 2046 bytes .../res/drawable-nodpi-v4/ic_ime_delete.png | Bin 0 -> 1280 bytes .../drawable-nodpi-v4/ic_ime_left_arrow.png | Bin 0 -> 298 bytes .../drawable-nodpi-v4/ic_ime_right_arrow.png | Bin 0 -> 297 bytes .../ic_ime_shift_lock_on.png | Bin 0 -> 586 bytes .../ic_ime_shift_lock_on_dark.png | Bin 0 -> 716 bytes .../ic_ime_shift_lock_on_dark2.png | Bin 0 -> 586 bytes .../ic_ime_shift_lock_on_dark3.png | Bin 0 -> 595 bytes .../ic_ime_shift_lock_on_white.png | Bin 0 -> 586 bytes .../drawable-nodpi-v4/ic_ime_shift_off.png | Bin 0 -> 717 bytes .../res/drawable-nodpi-v4/ic_ime_shift_on.png | Bin 0 -> 465 bytes .../res/drawable-nodpi-v4/ic_ime_space.png | Bin 0 -> 371 bytes .../res/drawable-nodpi-v4/ic_ime_symbols.png | Bin 0 -> 493 bytes .../res/drawable-nodpi-v4/ic_ime_voice.png | Bin 0 -> 5339 bytes .../res/drawable-nodpi-v4/ic_ime_world.png | Bin 0 -> 2423 bytes .../res/drawable-nodpi-v4/ic_launcher.png | Bin 0 -> 3164 bytes .../drawable-nodpi-v4/key_selector_square.png | Bin 0 -> 1194 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/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 + .../main/res/layout/activity_kb_layout.xml | 8 + .../src/main/res/layout/candidate.xml | 5 + .../src/main/res/layout/input_leanback.xml | 15 + .../main/res/layout/lang_selection_dialog.xml | 25 + .../layout/lang_selection_dialog_title.xml | 26 + .../src/main/res/layout/recognizer_view.xml | 8 + .../src/main/res/layout/root_leanback.xml | 23 + .../src/main/res/layout/selector.xml | 6 + .../res/mipmap-nodpi-v30/ic_launcher_main.png | Bin 0 -> 9559 bytes .../main/res/mipmap-nodpi/adaptive_icon.png | Bin 0 -> 8954 bytes .../main/res/mipmap-nodpi/ic_banner_main.png | Bin 0 -> 11837 bytes .../res/mipmap-nodpi/ic_launcher_main.png | Bin 0 -> 11447 bytes .../mipmap-nodpi/ic_launcher_main_round.png | Bin 0 -> 11634 bytes .../src/main/res/values-ar-rXB/strings.xml | 23 + .../src/main/res/values-ar/strings.xml | 64 + .../src/main/res/values-bg/strings.xml | 62 + .../src/main/res/values-de/strings.xml | 46 + .../src/main/res/values-de/themes.xml | 9 + .../src/main/res/values-el/strings.xml | 62 + leankeykeyboard/src/main/res/values-es | 64 + .../src/main/res/values-fa/strings.xml | 64 + .../src/main/res/values-fr/strings.xml | 62 + .../src/main/res/values-hdpi/dimens.xml | 35 + .../src/main/res/values-is-rIS/strings.xml | 62 + .../src/main/res/values-it/strings.xml | 62 + .../src/main/res/values-mdpi/dimens.xml | 35 + .../src/main/res/values-nl/strings.xml | 64 + .../src/main/res/values-ro/strings.xml | 63 + .../src/main/res/values-ro/themes.xml | 9 + .../src/main/res/values-ru/strings.xml | 53 + .../src/main/res/values-ru/themes.xml | 9 + .../src/main/res/values-sl/strings.xml | 62 + .../src/main/res/values-sv/strings.xml | 62 + .../src/main/res/values-th/strings.xml | 21 + .../src/main/res/values-tr/strings.xml | 62 + .../src/main/res/values-tvdpi/dimens.xml | 35 + .../src/main/res/values-uk/strings.xml | 53 + .../src/main/res/values-uk/themes.xml | 9 + .../src/main/res/values-xhdpi/dimens.xml | 35 + .../src/main/res/values/arrays.xml | 28 + .../res/values/bitmap_sound_level_view.xml | 16 + .../src/main/res/values/colors.xml | 12 + .../src/main/res/values/colors_dark.xml | 8 + .../src/main/res/values/colors_dark2.xml | 8 + .../src/main/res/values/colors_dark3.xml | 8 + .../src/main/res/values/colors_white.xml | 8 + .../src/main/res/values/dimens.xml | 38 + .../src/main/res/values/fractions.xml | 8 + .../src/main/res/values/integers.xml | 7 + .../src/main/res/values/languages.xml | 40 + .../res/values/leanback_keyboard_view.xml | 7 + .../src/main/res/values/strings.xml | 64 + .../src/main/res/values/styles.xml | 4 + .../src/main/res/values/text_appearance.xml | 41 + .../src/main/res/values/themes.xml | 9 + leankeykeyboard/src/main/res/xml/accent_a.xml | 17 + .../src/main/res/xml/accent_ar_qm.xml | 8 + .../src/main/res/xml/accent_ar_r.xml | 8 + .../src/main/res/xml/accent_bg_i.xml | 7 + leankeykeyboard/src/main/res/xml/accent_c.xml | 10 + leankeykeyboard/src/main/res/xml/accent_d.xml | 8 + .../src/main/res/xml/accent_de_b.xml | 7 + leankeykeyboard/src/main/res/xml/accent_e.xml | 16 + .../src/main/res/xml/accent_fa_a.xml | 7 + leankeykeyboard/src/main/res/xml/accent_g.xml | 9 + leankeykeyboard/src/main/res/xml/accent_i.xml | 13 + .../src/main/res/xml/accent_is_a.xml | 8 + .../src/main/res/xml/accent_is_e.xml | 8 + .../src/main/res/xml/accent_is_i.xml | 8 + .../src/main/res/xml/accent_is_o.xml | 8 + .../src/main/res/xml/accent_is_u.xml | 8 + .../src/main/res/xml/accent_is_y.xml | 8 + leankeykeyboard/src/main/res/xml/accent_k.xml | 8 + .../src/main/res/xml/accent_ka_q.xml | 8 + .../src/main/res/xml/accent_ko_kr_e.xml | 7 + .../src/main/res/xml/accent_ko_kr_o.xml | 7 + .../src/main/res/xml/accent_ko_kr_p.xml | 7 + .../src/main/res/xml/accent_ko_kr_q.xml | 7 + .../src/main/res/xml/accent_ko_kr_r.xml | 7 + .../src/main/res/xml/accent_ko_kr_t.xml | 7 + .../src/main/res/xml/accent_ko_kr_w.xml | 7 + 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 | 12 + .../src/main/res/xml/accent_slash.xml | 13 + leankeykeyboard/src/main/res/xml/accent_t.xml | 8 + leankeykeyboard/src/main/res/xml/accent_u.xml | 14 + .../src/main/res/xml/accent_voice.xml | 7 + leankeykeyboard/src/main/res/xml/accent_y.xml | 8 + leankeykeyboard/src/main/res/xml/accent_z.xml | 10 + .../src/main/res/xml/azerty_bg.xml | 68 + .../src/main/res/xml/azerty_en_us.xml | 65 + .../src/main/res/xml/azerty_fr.xml | 69 + .../src/main/res/xml/azerty_tr.xml | 69 + .../src/main/res/xml/empty_kbd.xml | 2 + 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_ar.xml | 66 + .../src/main/res/xml/qwerty_bg.xml | 69 + .../src/main/res/xml/qwerty_da.xml | 66 + .../src/main/res/xml/qwerty_de.xml | 71 + .../src/main/res/xml/qwerty_el.xml | 69 + .../src/main/res/xml/qwerty_en_us.xml | 66 + .../src/main/res/xml/qwerty_es_us.xml | 65 + .../src/main/res/xml/qwerty_fa.xml | 70 + .../src/main/res/xml/qwerty_fr.xml | 69 + .../src/main/res/xml/qwerty_he.xml | 69 + .../src/main/res/xml/qwerty_is.xml | 70 + .../src/main/res/xml/qwerty_it.xml | 69 + .../src/main/res/xml/qwerty_ka.xml | 66 + .../src/main/res/xml/qwerty_ko_kr.xml | 66 + .../src/main/res/xml/qwerty_nl.xml | 69 + .../src/main/res/xml/qwerty_ro.xml | 66 + .../src/main/res/xml/qwerty_ru.xml | 65 + .../src/main/res/xml/qwerty_sl.xml | 66 + .../src/main/res/xml/qwerty_sv.xml | 65 + .../src/main/res/xml/qwerty_th.xml | 69 + .../src/main/res/xml/qwerty_tr.xml | 69 + .../src/main/res/xml/qwerty_uk.xml | 65 + 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_us.xml | 65 + leankeykeyboard/src/main/res/xml/sym_eu.xml | 63 + leankeykeyboard/src/main/res/xml/sym_fr.xml | 63 + .../settings/KbActivationActivity.java | 119 ++ .../fragments/settings/AboutFragment.java | 69 + .../src/origin/res/values/strings.xml | 4 + .../settings/KbActivationActivity.java | 117 ++ .../fragments/settings/AboutFragment.java | 56 + .../src/playstore/res/values/strings.xml | 4 + .../leankeykeyboard/ExampleUnitTest.java | 17 + settings.gradle | 1 + 305 files changed, 16686 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE 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 img/leankeykeyboard_logo_small.png create mode 100644 img/leankeykeyboard_screenshot_01.png create mode 100644 img/leankeykeyboard_screenshot_02.png create mode 100644 img/leankeykeyboard_screenshot_03.png create mode 100644 img/screen4.png create mode 100644 img/screen5.png 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/liskovsoft/leankeyboard/activity/PermissionsActivity.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbLayoutActivity.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity2.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardBuilder.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardFactory.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardInfo.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardManager.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardStateManager.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOn.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnImpl.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnsFactory.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/Support.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/keyboards/ApkKeyboardAddOnAndBuilder.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/keyboards/ApkLangKeyboardFactory.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/BuildConfig.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogCatLogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/Logger.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/NullLogProvider.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlUtils.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlWriter.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/CheckedSource.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/KeyboardInfoAdapter.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardFactory.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardInfo.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/KeyboardWrapper.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/ResizeableLeanbackKeyboardView.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/theme/ThemeManager.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/ActivityListener.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerCallback.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentActivity.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentWrapper.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SearchCallback.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SystemVoiceDialog.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceDialog.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceOverlayDialog.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceSearchBridge.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/BaseSettingsFragment.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbLayoutFragment.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbSettingsFragment.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbThemeFragment.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/MiscFragment.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/AppInfoHelpers.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/Helpers.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/MessageHelpers.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/PermissionHelpers.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/EventLogTags.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/KeyMapperImeService.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackImeService.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardContainer.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardController.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardView.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackLocales.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackSuggestionsFactory.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackUtils.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavMotionTracker.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavSpaceTracker.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/BitmapSoundLevelView.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/RecognizerView.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/SpeechLevelSource.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/RestartServiceReceiver.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/TextUpdateReceiver.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LangUpdater.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LeanKeyPreferences.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleScript.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleUtility.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/TextDrawable.java create mode 100644 leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/widgets/DialogTitle.java 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_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-nodpi-v4/ic_ime_accent_close.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_alphabet.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_clipboard.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_delete.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_left_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_right_arrow.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_lock_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_lock_on_dark.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_lock_on_dark2.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_lock_on_dark3.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_lock_on_white.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_off.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_on.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_space.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_symbols.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_voice.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_world.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_launcher.png create mode 100644 leankeykeyboard/src/main/res/drawable-nodpi-v4/key_selector_square.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/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/activity_kb_layout.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/lang_selection_dialog.xml create mode 100644 leankeykeyboard/src/main/res/layout/lang_selection_dialog_title.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-nodpi-v30/ic_launcher_main.png create mode 100644 leankeykeyboard/src/main/res/mipmap-nodpi/adaptive_icon.png create mode 100644 leankeykeyboard/src/main/res/mipmap-nodpi/ic_banner_main.png create mode 100644 leankeykeyboard/src/main/res/mipmap-nodpi/ic_launcher_main.png create mode 100644 leankeykeyboard/src/main/res/mipmap-nodpi/ic_launcher_main_round.png create mode 100644 leankeykeyboard/src/main/res/values-ar-rXB/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-ar/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-bg/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-de/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-de/themes.xml create mode 100644 leankeykeyboard/src/main/res/values-el/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-es create mode 100644 leankeykeyboard/src/main/res/values-fa/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-fr/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-hdpi/dimens.xml create mode 100644 leankeykeyboard/src/main/res/values-is-rIS/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-it/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-mdpi/dimens.xml create mode 100644 leankeykeyboard/src/main/res/values-nl/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-ro/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-ro/themes.xml create mode 100644 leankeykeyboard/src/main/res/values-ru/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-ru/themes.xml create mode 100644 leankeykeyboard/src/main/res/values-sl/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-sv/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-th/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-tr/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-tvdpi/dimens.xml create mode 100644 leankeykeyboard/src/main/res/values-uk/strings.xml create mode 100644 leankeykeyboard/src/main/res/values-uk/themes.xml create mode 100644 leankeykeyboard/src/main/res/values-xhdpi/dimens.xml 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/colors_dark.xml create mode 100644 leankeykeyboard/src/main/res/values/colors_dark2.xml create mode 100644 leankeykeyboard/src/main/res/values/colors_dark3.xml create mode 100644 leankeykeyboard/src/main/res/values/colors_white.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/languages.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/values/styles.xml create mode 100644 leankeykeyboard/src/main/res/values/text_appearance.xml create mode 100644 leankeykeyboard/src/main/res/values/themes.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_a.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ar_qm.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ar_r.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_bg_i.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_de_b.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_e.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_fa_a.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_is_a.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_is_e.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_is_i.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_is_o.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_is_u.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_is_y.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_k.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ka_q.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_e.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_o.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_p.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_q.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_r.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_t.xml create mode 100644 leankeykeyboard/src/main/res/xml/accent_ko_kr_w.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_slash.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_voice.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_bg.xml create mode 100644 leankeykeyboard/src/main/res/xml/azerty_en_us.xml create mode 100644 leankeykeyboard/src/main/res/xml/azerty_fr.xml create mode 100644 leankeykeyboard/src/main/res/xml/azerty_tr.xml create mode 100644 leankeykeyboard/src/main/res/xml/empty_kbd.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_ar.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_bg.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_da.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_de.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_el.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_en_us.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_es_us.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_fa.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_fr.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_he.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_is.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_it.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_ka.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_ko_kr.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_nl.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_ro.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_ru.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_sl.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_sv.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_th.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_tr.xml create mode 100644 leankeykeyboard/src/main/res/xml/qwerty_uk.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_us.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/origin/java/com/liskovsoft/leankeyboard/activity/settings/KbActivationActivity.java create mode 100644 leankeykeyboard/src/origin/java/com/liskovsoft/leankeyboard/fragments/settings/AboutFragment.java create mode 100644 leankeykeyboard/src/origin/res/values/strings.xml create mode 100644 leankeykeyboard/src/playstore/java/com/liskovsoft/leankeyboard/activity/settings/KbActivationActivity.java create mode 100644 leankeykeyboard/src/playstore/java/com/liskovsoft/leankeyboard/fragments/settings/AboutFragment.java create mode 100644 leankeykeyboard/src/playstore/res/values/strings.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..7b1d424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/screen.png +notes.txt +/other +/files +/misc +/releases +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/LICENSE b/LICENSE new file mode 100644 index 0000000..359dc59 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020-present Yurii L + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9173e39 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +![Logo of LeanKeyboard](img/leankeykeyboard_logo_small.png "Logo of LeanKeyboard") LeanKeyboard +========= + +[![MPLv2 License](http://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/) + +__LeanKeyboard: Keyboard for Android-based set-top boxes and TVs:__ + + * Google Play page + * Telegram group + +### Features: + * Designed for TV screens. + * Any remote controller support. + * Supports dozens of languages. + * Doesn't depend on Google Services. + * __No root required!__ + +__Tip: Switch to other language with language button or by long press on the space bar__ + +__Tip: Do long press on the language button to choose between available languages__ + +### Screenshots: + * __[Open screenshots](#screens)__ + +### Install LeanKeyboard: +__Easy installation in less than 10 minutes with only FireTV__ + * Install LeanKeyKeyboard (only FireTV needed) + +__Standard installation via ADB__ + * If you don't know how to sideload/install apps via ADB, read a tutorial (e.g. this one) + * Download latest LeanKeyKeyboard APK and sideload/install with adb: + * *adb install -r LeanKeyboard.apk* + * Enjoy :) + +### Donation: +If you want to support my developments you are welcome to buy me a cup of coffee :) + + + * [**Patreon**](https://www.patreon.com/yuliskov) + * **PayPal**: firsthash at gmail.com + * **BTC**: 1JAT5VVWarVBkpVbNDn8UA8HXNdrukuBSx + * **LTC**: ltc1qgc24eq9jl9cq78qnd5jpqhemkajg9vudwyd8pw + * **ETH**: 0xe455E21a085ae195a097cd4F456051A9916A5064 + * **ETC**: 0x209eCd33Fa61fA92167595eB3Aea92EE1905c815 + * **XMR**: 48QsMjqfkeW54vkgKyRnjodtYxdmLk6HXfTWPSZoaFPEDpoHDwFUciGCe1QC9VAeGrgGw4PKNAksX9RW7myFqYJQDN5cHGT + * **BNB**: bnb1amjr7fauftxxyhe4f95280vklctj243k9u55fq + * **DOGE**: DBnqJwJs2GJBxrCDsi5bXwSmjnz8uGdUpB + * **eUSDT**: 0xe455e21a085ae195a097cd4f456051a9916a5064 + +### Reviews / Articles: + * [__XDA Discussion__](https://forum.xda-developers.com/fire-tv/general/guide-change-screen-keyboard-to-leankey-t3527675) + +### Changelog: + * [Check releases page for changelog ..](https://github.com/yuliskov/LeanKeyboard/releases) + +### Contributors: + * __[aglt](https://github.com/aglt)__ (Icelandic lang) + * __[rabin111](https://github.com/rabin111)__ (Thai lang) + +### Developer: + * __[yuliskov](https://github.com/yuliskov)__ (design & coding) + +### Screens: +![Screenshot of LeanKeyboard](img/leankeykeyboard_screenshot_01.png "Screenshot of LeanKeyboard") +![Screenshot of LeanKeyboard](img/leankeykeyboard_screenshot_02.png "Screenshot of LeanKeyboard") +![Screenshot of LeanKeyboard](img/leankeykeyboard_screenshot_03.png "Screenshot of LeanKeyboard") diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..bde32dc --- /dev/null +++ b/build.gradle @@ -0,0 +1,78 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + // jcenter() + // maven { + // url 'https://maven.google.com/' + // name 'Google' + // } + // google() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.5.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 { + // Google Play SDK version requirements: + // https://support.google.com/googleplay/android-developer/answer/11926878 + compileSdkVersion = 35 + buildToolsVersion = "35.0.0" + minSdkVersion = 14 + targetSdkVersion = 35 + espressoCoreVersion = 'com.android.support.test.espresso:espresso-core:2.2.2' + junitVersion = 'junit:junit:4.12' + robolectricVersion = 'org.robolectric:robolectric:3.5.1' + crashlyticsVersion = 'com.crashlytics.sdk.android:crashlytics:2.8.0@aar' + // androidx migration: + // https://developer.android.com/jetpack/androidx/migrate + // https://developer.android.com/jetpack/androidx/migrate/artifact-mappings + appCompatXVersion = 'androidx.appcompat:appcompat:1.1.0' + constraintXVersion = 'androidx.constraintlayout:constraintlayout:1.1.3' + supportXVersion = 'androidx.legacy:legacy-support-v4:1.0.0' + leanbackCompatXVersion = 'androidx.leanback:leanback:1.0.0' + designXVersion = 'com.google.android.material:material:1.0.0' + voiceOverlayVersion = 'com.algolia.instantsearch:voice:1.1.0' // https://github.com/algolia/voice-overlay-android + } +} + +allprojects { + repositories { + google() + mavenCentral() + //jcenter() + // com.android.support libs + //maven { url 'https://maven.google.com' } + } + + gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +// Fix 'Namespace not specified' +// https://stackoverflow.com/questions/76300671/android-getting-error-namespace-not-specified +// subprojects { +// afterEvaluate { project -> +// if (project.hasProperty('android')) { +// project.android { +// if (namespace == null) { +// namespace project.group +// } +// } +// } +// } +// } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7f5325b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +android.enableJetifier=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 zcmafaW0a=B^559DjdyHo$F^PVt zzd|cWgMz^T0YO0lQ8%TE1O06v|NZl~LH{LLQ58WtNjWhFP#}eWVO&eiP!jmdp!%24 z{&z-MK{-h=QDqf+S+Pgi=_wg$I{F28X*%lJ>A7Yl#$}fMhymMu?R9TEB?#6@|Q^e^AHhxcRL$z1gsc`-Q`3j+eYAd<4@z^{+?JM8bmu zSVlrVZ5-)SzLn&LU9GhXYG{{I+u(+6ES+tAtQUanYC0^6kWkks8cG;C&r1KGs)Cq}WZSd3k1c?lkzwLySimkP5z)T2Ox3pNs;PdQ=8JPDkT7#0L!cV? zzn${PZs;o7UjcCVd&DCDpFJvjI=h(KDmdByJuDYXQ|G@u4^Kf?7YkE67fWM97kj6F z973tGtv!k$k{<>jd~D&c(x5hVbJa`bILdy(00%lY5}HZ2N>)a|))3UZ&fUa5@uB`H z+LrYm@~t?g`9~@dFzW5l>=p0hG%rv0>(S}jEzqQg6-jImG%Pr%HPtqIV_Ym6yRydW z4L+)NhcyYp*g#vLH{1lK-hQQSScfvNiNx|?nSn-?cc8}-9~Z_0oxlr~(b^EiD`Mx< zlOLK)MH?nl4dD|hx!jBCIku-lI(&v~bCU#!L7d0{)h z;k4y^X+=#XarKzK*)lv0d6?kE1< zmCG^yDYrSwrKIn04tG)>>10%+ zEKzs$S*Zrl+GeE55f)QjY$ zD5hi~J17k;4VSF_`{lPFwf^Qroqg%kqM+Pdn%h#oOPIsOIwu?JR717atg~!)*CgXk zERAW?c}(66rnI+LqM^l7BW|9dH~5g1(_w$;+AAzSYlqop*=u5}=g^e0xjlWy0cUIT7{Fs2Xqx*8% zW71JB%hk%aV-wjNE0*$;E-S9hRx5|`L2JXxz4TX3nf8fMAn|523ssV;2&145zh{$V z#4lt)vL2%DCZUgDSq>)ei2I`*aeNXHXL1TB zC8I4!uq=YYVjAdcCjcf4XgK2_$y5mgsCdcn2U!VPljXHco>+%`)6W=gzJk0$e%m$xWUCs&Ju-nUJjyQ04QF_moED2(y6q4l+~fo845xm zE5Esx?~o#$;rzpCUk2^2$c3EBRNY?wO(F3Pb+<;qfq;JhMFuSYSxiMejBQ+l8(C-- zz?Xufw@7{qvh$;QM0*9tiO$nW(L>83egxc=1@=9Z3)G^+*JX-z92F((wYiK>f;6 zkc&L6k4Ua~FFp`x7EF;ef{hb*n8kx#LU|6{5n=A55R4Ik#sX{-nuQ}m7e<{pXq~8#$`~6| zi{+MIgsBRR-o{>)CE8t0Bq$|SF`M0$$7-{JqwFI1)M^!GMwq5RAWMP!o6G~%EG>$S zYDS?ux;VHhRSm*b^^JukYPVb?t0O%^&s(E7Rb#TnsWGS2#FdTRj_SR~YGjkaRFDI=d)+bw$rD;_!7&P2WEmn zIqdERAbL&7`iA^d?8thJ{(=)v>DgTF7rK-rck({PpYY$7uNY$9-Z< ze4=??I#p;$*+-Tm!q8z}k^%-gTm59^3$*ByyroqUe02Dne4?Fc%JlO>*f9Zj{++!^ zBz0FxuS&7X52o6-^CYq>jkXa?EEIfh?xdBPAkgpWpb9Tam^SXoFb3IRfLwanWfskJ zIbfU-rJ1zPmOV)|%;&NSWIEbbwj}5DIuN}!m7v4($I{Rh@<~-sK{fT|Wh?<|;)-Z; zwP{t@{uTsmnO@5ZY82lzwl4jeZ*zsZ7w%a+VtQXkigW$zN$QZnKw4F`RG`=@eWowO zFJ6RC4e>Y7Nu*J?E1*4*U0x^>GK$>O1S~gkA)`wU2isq^0nDb`);Q(FY<8V6^2R%= zDY}j+?mSj{bz2>F;^6S=OLqiHBy~7h4VVscgR#GILP!zkn68S^c04ZL3e$lnSU_(F zZm3e`1~?eu1>ys#R6>Gu$`rWZJG&#dsZ?^)4)v(?{NPt+_^Ak>Ap6828Cv^B84fa4 z_`l$0SSqkBU}`f*H#<14a)khT1Z5Z8;=ga^45{l8y*m|3Z60vgb^3TnuUKaa+zP;m zS`za@C#Y;-LOm&pW||G!wzr+}T~Q9v4U4ufu*fLJC=PajN?zN=?v^8TY}wrEeUygdgwr z7szml+(Bar;w*c^!5txLGKWZftqbZP`o;Kr1)zI}0Kb8yr?p6ZivtYL_KA<+9)XFE z=pLS5U&476PKY2aKEZh}%|Vb%!us(^qf)bKdF7x_v|Qz8lO7Ro>;#mxG0gqMaTudL zi2W!_#3@INslT}1DFJ`TsPvRBBGsODklX0`p-M6Mrgn~6&fF`kdj4K0I$<2Hp(YIA z)fFdgR&=qTl#sEFj6IHzEr1sYM6 zNfi!V!biByA&vAnZd;e_UfGg_={}Tj0MRt3SG%BQYnX$jndLG6>ssgIV{T3#=;RI% zE}b!9z#fek19#&nFgC->@!IJ*Fe8K$ZOLmg|6(g}ccsSBpc`)3;Ar8;3_k`FQ#N9&1tm>c|2mzG!!uWvelm zJj|oDZ6-m(^|dn3em(BF&3n12=hdtlb@%!vGuL*h`CXF?^=IHU%Q8;g8vABm=U!vX zT%Ma6gpKQC2c;@wH+A{)q+?dAuhetSxBDui+Z;S~6%oQq*IwSMu-UhMDy{pP z-#GB-a0`0+cJ%dZ7v0)3zfW$eV>w*mgU4Cma{P$DY3|w364n$B%cf()fZ;`VIiK_O zQ|q|(55+F$H(?opzr%r)BJLy6M&7Oq8KCsh`pA5^ohB@CDlMKoDVo5gO&{0k)R0b(UOfd>-(GZGeF}y?QI_T+GzdY$G{l!l% zHyToqa-x&X4;^(-56Lg$?(KYkgJn9W=w##)&CECqIxLe@+)2RhO*-Inpb7zd8txFG6mY8E?N8JP!kRt_7-&X{5P?$LAbafb$+hkA*_MfarZxf zXLpXmndnV3ubbXe*SYsx=eeuBKcDZI0bg&LL-a8f9>T(?VyrpC6;T{)Z{&|D5a`Aa zjP&lP)D)^YYWHbjYB6ArVs+4xvrUd1@f;;>*l zZH``*BxW+>Dd$be{`<&GN(w+m3B?~3Jjz}gB8^|!>pyZo;#0SOqWem%xeltYZ}KxOp&dS=bg|4 zY-^F~fv8v}u<7kvaZH`M$fBeltAglH@-SQres30fHC%9spF8Ld%4mjZJDeGNJR8+* zl&3Yo$|JYr2zi9deF2jzEC) zl+?io*GUGRp;^z+4?8gOFA>n;h%TJC#-st7#r&-JVeFM57P7rn{&k*z@+Y5 zc2sui8(gFATezp|Te|1-Q*e|Xi+__8bh$>%3|xNc2kAwTM!;;|KF6cS)X3SaO8^z8 zs5jV(s(4_NhWBSSJ}qUzjuYMKlkjbJS!7_)wwVsK^qDzHx1u*sC@C1ERqC#l%a zk>z>m@sZK{#GmsB_NkEM$$q@kBrgq%=NRBhL#hjDQHrI7(XPgFvP&~ZBJ@r58nLme zK4tD}Nz6xrbvbD6DaDC9E_82T{(WRQBpFc+Zb&W~jHf1MiBEqd57}Tpo8tOXj@LcF zwN8L-s}UO8%6piEtTrj@4bLH!mGpl5mH(UJR1r9bBOrSt0tSJDQ9oIjcW#elyMAxl7W^V(>8M~ss0^>OKvf{&oUG@uW{f^PtV#JDOx^APQKm& z{*Ysrz&ugt4PBUX@KERQbycxP%D+ApR%6jCx7%1RG2YpIa0~tqS6Xw6k#UN$b`^l6d$!I z*>%#Eg=n#VqWnW~MurJLK|hOQPTSy7G@29g@|g;mXC%MF1O7IAS8J^Q6D&Ra!h^+L&(IBYg2WWzZjT-rUsJMFh@E)g)YPW_)W9GF3 zMZz4RK;qcjpnat&J;|MShuPc4qAc)A| zVB?h~3TX+k#Cmry90=kdDoPYbhzs#z96}#M=Q0nC{`s{3ZLU)c(mqQQX;l~1$nf^c zFRQ~}0_!cM2;Pr6q_(>VqoW0;9=ZW)KSgV-c_-XdzEapeLySavTs5-PBsl-n3l;1jD z9^$^xR_QKDUYoeqva|O-+8@+e??(pRg@V|=WtkY!_IwTN~ z9Rd&##eWt_1w$7LL1$-ETciKFyHnNPjd9hHzgJh$J(D@3oYz}}jVNPjH!viX0g|Y9 zDD`Zjd6+o+dbAbUA( zEqA9mSoX5p|9sDVaRBFx_8)Ra4HD#xDB(fa4O8_J2`h#j17tSZOd3%}q8*176Y#ak zC?V8Ol<*X{Q?9j{Ys4Bc#sq!H;^HU$&F_`q2%`^=9DP9YV-A!ZeQ@#p=#ArloIgUH%Y-s>G!%V3aoXaY=f<UBrJTN+*8_lMX$yC=Vq+ zrjLn-pO%+VIvb~>k%`$^aJ1SevcPUo;V{CUqF>>+$c(MXxU12mxqyFAP>ki{5#;Q0 zx7Hh2zZdZzoxPY^YqI*Vgr)ip0xnpQJ+~R*UyFi9RbFd?<_l8GH@}gGmdB)~V7vHg z>Cjy78TQTDwh~+$u$|K3if-^4uY^|JQ+rLVX=u7~bLY29{lr>jWV7QCO5D0I>_1?; zx>*PxE4|wC?#;!#cK|6ivMzJ({k3bT_L3dHY#h7M!ChyTT`P#%3b=k}P(;QYTdrbe z+e{f@we?3$66%02q8p3;^th;9@y2vqt@LRz!DO(WMIk?#Pba85D!n=Ao$5NW0QVgS zoW)fa45>RkjU?H2SZ^#``zs6dG@QWj;MO4k6tIp8ZPminF`rY31dzv^e-3W`ZgN#7 z)N^%Rx?jX&?!5v`hb0-$22Fl&UBV?~cV*{hPG6%ml{k;m+a-D^XOF6DxPd$3;2VVY zT)E%m#ZrF=D=84$l}71DK3Vq^?N4``cdWn3 zqV=mX1(s`eCCj~#Nw4XMGW9tK>$?=cd$ule0Ir8UYzhi?%_u0S?c&j7)-~4LdolkgP^CUeE<2`3m)I^b ztV`K0k$OS^-GK0M0cNTLR22Y_eeT{<;G(+51Xx}b6f!kD&E4; z&Op8;?O<4D$t8PB4#=cWV9Q*i4U+8Bjlj!y4`j)^RNU#<5La6|fa4wLD!b6?RrBsF z@R8Nc^aO8ty7qzlOLRL|RUC-Bt-9>-g`2;@jfNhWAYciF{df9$n#a~28+x~@x0IWM zld=J%YjoKm%6Ea>iF){z#|~fo_w#=&&HRogJmXJDjCp&##oVvMn9iB~gyBlNO3B5f zXgp_1I~^`A0z_~oAa_YBbNZbDsnxLTy0@kkH!=(xt8|{$y<+|(wSZW7@)#|fs_?gU5-o%vpsQPRjIxq;AED^oG%4S%`WR}2(*!84Pe8Jw(snJ zq~#T7+m|w#acH1o%e<+f;!C|*&_!lL*^zRS`;E}AHh%cj1yR&3Grv&0I9k9v0*w8^ zXHEyRyCB`pDBRAxl;ockOh6$|7i$kzCBW$}wGUc|2bo3`x*7>B@eI=-7lKvI)P=gQ zf_GuA+36kQb$&{ZH)6o^x}wS}S^d&Xmftj%nIU=>&j@0?z8V3PLb1JXgHLq)^cTvB zFO6(yj1fl1Bap^}?hh<>j?Jv>RJdK{YpGjHxnY%d8x>A{k+(18J|R}%mAqq9Uzm8^Us#Ir_q^w9-S?W07YRD`w%D(n;|8N%_^RO`zp4 z@`zMAs>*x0keyE)$dJ8hR37_&MsSUMlGC*=7|wUehhKO)C85qoU}j>VVklO^TxK?! zO!RG~y4lv#W=Jr%B#sqc;HjhN={wx761vA3_$S>{j+r?{5=n3le|WLJ(2y_r>{)F_ z=v8Eo&xFR~wkw5v-{+9^JQukxf8*CXDWX*ZzjPVDc>S72uxAcY+(jtg3ns_5R zRYl2pz`B)h+e=|7SfiAAP;A zk0tR)3u1qy0{+?bQOa17SpBRZ5LRHz(TQ@L0%n5xJ21ri>^X420II1?5^FN3&bV?( zCeA)d9!3FAhep;p3?wLPs`>b5Cd}N!;}y`Hq3ppDs0+><{2ey0yq8o7m-4|oaMsWf zsLrG*aMh91drd-_QdX6t&I}t2!`-7$DCR`W2yoV%bcugue)@!SXM}fJOfG(bQQh++ zjAtF~zO#pFz})d8h)1=uhigDuFy`n*sbxZ$BA^Bt=Jdm}_KB6sCvY(T!MQnqO;TJs zVD{*F(FW=+v`6t^6{z<3-fx#|Ze~#h+ymBL^^GKS%Ve<)sP^<4*y_Y${06eD zH_n?Ani5Gs4&1z)UCL-uBvq(8)i!E@T_*0Sp5{Ddlpgke^_$gukJc_f9e=0Rfpta@ ze5~~aJBNK&OJSw!(rDRAHV0d+eW#1?PFbr==uG-$_fu8`!DWqQD~ef-Gx*ZmZx33_ zb0+I(0!hIK>r9_S5A*UwgRBKSd6!ieiYJHRigU@cogJ~FvJHY^DSysg)ac=7#wDBf zNLl!E$AiUMZC%%i5@g$WsN+sMSoUADKZ}-Pb`{7{S>3U%ry~?GVX!BDar2dJHLY|g zTJRo#Bs|u#8ke<3ohL2EFI*n6adobnYG?F3-#7eZZQO{#rmM8*PFycBR^UZKJWr(a z8cex$DPOx_PL^TO<%+f^L6#tdB8S^y#+fb|acQfD(9WgA+cb15L+LUdHKv)wE6={i zX^iY3N#U7QahohDP{g`IHS?D00eJC9DIx0V&nq!1T* z4$Bb?trvEG9JixrrNRKcjX)?KWR#Y(dh#re_<y*=5!J+-Wwb*D>jKXgr5L8_b6pvSAn3RIvI5oj!XF^m?otNA=t^dg z#V=L0@W)n?4Y@}49}YxQS=v5GsIF3%Cp#fFYm0Bm<}ey& zOfWB^vS8ye?n;%yD%NF8DvOpZqlB++#4KnUj>3%*S(c#yACIU>TyBG!GQl7{b8j#V z;lS})mrRtT!IRh2B-*T58%9;!X}W^mg;K&fb7?2#JH>JpCZV5jbDfOgOlc@wNLfHN z8O92GeBRjCP6Q9^Euw-*i&Wu=$>$;8Cktx52b{&Y^Ise-R1gTKRB9m0*Gze>$k?$N zua_0Hmbcj8qQy{ZyJ%`6v6F+yBGm>chZxCGpeL@os+v&5LON7;$tb~MQAbSZKG$k z8w`Mzn=cX4Hf~09q8_|3C7KnoM1^ZGU}#=vn1?1^Kc-eWv4x^T<|i9bCu;+lTQKr- zRwbRK!&XrWRoO7Kw!$zNQb#cJ1`iugR(f_vgmu!O)6tFH-0fOSBk6$^y+R07&&B!(V#ZV)CX42( zTC(jF&b@xu40fyb1=_2;Q|uPso&Gv9OSM1HR{iGPi@JUvmYM;rkv#JiJZ5-EFA%Lu zf;wAmbyclUM*D7>^nPatbGr%2aR5j55qSR$hR`c?d+z z`qko8Yn%vg)p=H`1o?=b9K0%Blx62gSy)q*8jWPyFmtA2a+E??&P~mT@cBdCsvFw4 zg{xaEyVZ|laq!sqN}mWq^*89$e6%sb6Thof;ml_G#Q6_0-zwf80?O}D0;La25A0C+ z3)w-xesp6?LlzF4V%yA9Ryl_Kq*wMk4eu&)Tqe#tmQJtwq`gI^7FXpToum5HP3@;N zpe4Y!wv5uMHUu`zbdtLys5)(l^C(hFKJ(T)z*PC>7f6ZRR1C#ao;R&_8&&a3)JLh* zOFKz5#F)hJqVAvcR#1)*AWPGmlEKw$sQd)YWdAs_W-ojA?Lm#wCd}uF0^X=?AA#ki zWG6oDQZJ5Tvifdz4xKWfK&_s`V*bM7SVc^=w7-m}jW6U1lQEv_JsW6W(| zkKf>qn^G!EWn~|7{G-&t0C6C%4)N{WRK_PM>4sW8^dDkFM|p&*aBuN%fg(I z^M-49vnMd%=04N95VO+?d#el>LEo^tvnQsMop70lNqq@%cTlht?e+B5L1L9R4R(_6 z!3dCLeGXb+_LiACNiqa^nOELJj%q&F^S+XbmdP}`KAep%TDop{Pz;UDc#P&LtMPgH zy+)P1jdgZQUuwLhV<89V{3*=Iu?u#v;v)LtxoOwV(}0UD@$NCzd=id{UuDdedeEp| z`%Q|Y<6T?kI)P|8c!K0Za&jxPhMSS!T`wlQNlkE(2B*>m{D#`hYYD>cgvsKrlcOcs7;SnVCeBiK6Wfho@*Ym9 zr0zNfrr}0%aOkHd)d%V^OFMI~MJp+Vg-^1HPru3Wvac@-QjLX9Dx}FL(l>Z;CkSvC zOR1MK%T1Edv2(b9$ttz!E7{x4{+uSVGz`uH&)gG`$)Vv0^E#b&JSZp#V)b6~$RWwe zzC3FzI`&`EDK@aKfeqQ4M(IEzDd~DS>GB$~ip2n!S%6sR&7QQ*=Mr(v*v-&07CO%# zMBTaD8-EgW#C6qFPPG1Ph^|0AFs;I+s|+A@WU}%@WbPI$S0+qFR^$gim+Fejs2f!$ z@Xdlb_K1BI;iiOUj`j+gOD%mjq^S~J0cZZwuqfzNH9}|(vvI6VO+9ZDA_(=EAo;( zKKzm`k!s!_sYCGOm)93Skaz+GF7eY@Ra8J$C)`X)`aPKym?7D^SI}Mnef4C@SgIEB z>nONSFl$qd;0gSZhNcRlq9VVHPkbakHlZ1gJ1y9W+@!V$TLpdsbKR-VwZrsSM^wLr zL9ob&JG)QDTaf&R^cnm5T5#*J3(pSpjM5~S1 z@V#E2syvK6wb?&h?{E)CoI~9uA(hST7hx4_6M(7!|BW3TR_9Q zLS{+uPoNgw(aK^?=1rFcDO?xPEk5Sm=|pW%-G2O>YWS^(RT)5EQ2GSl75`b}vRcD2 z|HX(x0#Qv+07*O|vMIV(0?KGjOny#Wa~C8Q(kF^IR8u|hyyfwD&>4lW=)Pa311caC zUk3aLCkAFkcidp@C%vNVLNUa#1ZnA~ZCLrLNp1b8(ndgB(0zy{Mw2M@QXXC{hTxr7 zbipeHI-U$#Kr>H4}+cu$#2fG6DgyWgq{O#8aa)4PoJ^;1z7b6t&zt zPei^>F1%8pcB#1`z`?f0EAe8A2C|}TRhzs*-vN^jf(XNoPN!tONWG=abD^=Lm9D?4 zbq4b(in{eZehKC0lF}`*7CTzAvu(K!eAwDNC#MlL2~&gyFKkhMIF=32gMFLvKsbLY z1d$)VSzc^K&!k#2Q?(f>pXn){C+g?vhQ0ijV^Z}p5#BGrGb%6n>IH-)SA$O)*z3lJ z1rtFlovL`cC*RaVG!p!4qMB+-f5j^1)ALf4Z;2X&ul&L!?`9Vdp@d(%(>O=7ZBV;l z?bbmyPen>!P{TJhSYPmLs759b1Ni1`d$0?&>OhxxqaU|}-?Z2c+}jgZ&vCSaCivx| z-&1gw2Lr<;U-_xzlg}Fa_3NE?o}R-ZRX->__}L$%2ySyiPegbnM{UuADqwDR{C2oS zPuo88%DNfl4xBogn((9j{;*YGE0>2YoL?LrH=o^SaAcgO39Ew|vZ0tyOXb509#6{7 z0<}CptRX5(Z4*}8CqCgpT@HY3Q)CvRz_YE;nf6ZFwEje^;Hkj0b1ESI*8Z@(RQrW4 z35D5;S73>-W$S@|+M~A(vYvX(yvLN(35THo!yT=vw@d(=q8m+sJyZMB7T&>QJ=jkwQVQ07*Am^T980rldC)j}}zf!gq7_z4dZ zHwHB94%D-EB<-^W@9;u|(=X33c(G>q;Tfq1F~-Lltp|+uwVzg?e$M96ndY{Lcou%w zWRkjeE`G*i)Bm*|_7bi+=MPm8by_};`=pG!DSGBP6y}zvV^+#BYx{<>p0DO{j@)(S zxcE`o+gZf8EPv1g3E1c3LIbw+`rO3N+Auz}vn~)cCm^DlEi#|Az$b z2}Pqf#=rxd!W*6HijC|u-4b~jtuQS>7uu{>wm)PY6^S5eo=?M>;tK`=DKXuArZvaU zHk(G??qjKYS9G6Du)#fn+ob=}C1Hj9d?V$_=J41ljM$CaA^xh^XrV-jzi7TR-{{9V zZZI0;aQ9YNEc`q=Xvz;@q$eqL<}+L(>HR$JA4mB6~g*YRSnpo zTofY;u7F~{1Pl=pdsDQx8Gg#|@BdoWo~J~j%DfVlT~JaC)he>he6`C`&@@#?;e(9( zgKcmoidHU$;pi{;VXyE~4>0{kJ>K3Uy6`s*1S--*mM&NY)*eOyy!7?9&osK*AQ~vi z{4qIQs)s#eN6j&0S()cD&aCtV;r>ykvAzd4O-fG^4Bmx2A2U7-kZR5{Qp-R^i4H2yfwC7?9(r3=?oH(~JR4=QMls>auMv*>^^!$}{}R z;#(gP+O;kn4G|totqZGdB~`9yzShMze{+$$?9%LJi>4YIsaPMwiJ{`gocu0U}$Q$vI5oeyKrgzz>!gI+XFt!#n z7vs9Pn`{{5w-@}FJZn?!%EQV!PdA3hw%Xa2#-;X4*B4?`WM;4@bj`R-yoAs_t4!!` zEaY5OrYi`3u3rXdY$2jZdZvufgFwVna?!>#t#DKAD2;U zqpqktqJ)8EPY*w~yj7r~#bNk|PDM>ZS?5F7T5aPFVZrqeX~5_1*zTQ%;xUHe#li?s zJ*5XZVERVfRjwX^s=0<%nXhULK+MdibMjzt%J7#fuh?NXyJ^pqpfG$PFmG!h*opyi zmMONjJY#%dkdRHm$l!DLeBm#_0YCq|x17c1fYJ#5YMpsjrFKyU=y>g5QcTgbDm28X zYL1RK)sn1@XtkGR;tNb}(kg#9L=jNSbJizqAgV-TtK2#?LZXrCIz({ zO^R|`ZDu(d@E7vE}df5`a zNIQRp&mDFbgyDKtyl@J|GcR9!h+_a$za$fnO5Ai9{)d7m@?@qk(RjHwXD}JbKRn|u z=Hy^z2vZ<1Mf{5ihhi9Y9GEG74Wvka;%G61WB*y7;&L>k99;IEH;d8-IR6KV{~(LZ zN7@V~f)+yg7&K~uLvG9MAY+{o+|JX?yf7h9FT%7ZrW7!RekjwgAA4jU$U#>_!ZC|c zA9%tc9nq|>2N1rg9uw-Qc89V}I5Y`vuJ(y`Ibc_?D>lPF0>d_mB@~pU`~)uWP48cT@fTxkWSw{aR!`K{v)v zpN?vQZZNPgs3ki9h{An4&Cap-c5sJ!LVLtRd=GOZ^bUpyDZHm6T|t#218}ZA zx*=~9PO>5IGaBD^XX-_2t7?7@WN7VfI^^#Csdz9&{1r z9y<9R?BT~-V8+W3kzWWQ^)ZSI+R zt^Lg`iN$Z~a27)sC_03jrD-%@{ArCPY#Pc*u|j7rE%}jF$LvO4vyvAw3bdL_mg&ei zXys_i=Q!UoF^Xp6^2h5o&%cQ@@)$J4l`AG09G6Uj<~A~!xG>KjKSyTX)zH*EdHMK0 zo;AV-D+bqWhtD-!^+`$*P0B`HokilLd1EuuwhJ?%3wJ~VXIjIE3tj653PExvIVhE& zFMYsI(OX-Q&W$}9gad^PUGuKElCvXxU_s*kx%dH)Bi&$*Q(+9j>(Q>7K1A#|8 zY!G!p0kW29rP*BNHe_wH49bF{K7tymi}Q!Vc_Ox2XjwtpM2SYo7n>?_sB=$c8O5^? z6as!fE9B48FcE`(ruNXP%rAZlDXrFTC7^aoXEX41k)tIq)6kJ*(sr$xVqsh_m3^?? zOR#{GJIr6E0Sz{-( z-R?4asj|!GVl0SEagNH-t|{s06Q3eG{kZOoPHL&Hs0gUkPc&SMY=&{C0&HDI)EHx9 zm#ySWluxwp+b~+K#VG%21%F65tyrt9RTPR$eG0afer6D`M zTW=y!@y6yi#I5V#!I|8IqU=@IfZo!@9*P+f{yLxGu$1MZ%xRY(gRQ2qH@9eMK0`Z> zgO`4DHfFEN8@m@dxYuljsmVv}c4SID+8{kr>d_dLzF$g>urGy9g+=`xAfTkVtz56G zrKNsP$yrDyP=kIqPN9~rVmC-wH672NF7xU>~j5M06Xr&>UJBmOV z%7Ie2d=K=u^D`~i3(U7x?n=h!SCSD1`aFe-sY<*oh+=;B>UVFBOHsF=(Xr(Cai{dL z4S7Y>PHdfG9Iav5FtKzx&UCgg)|DRLvq7!0*9VD`e6``Pgc z1O!qSaNeBBZnDXClh(Dq@XAk?Bd6+_rsFt`5(E+V2c)!Mx4X z47X+QCB4B7$B=Fw1Z1vnHg;x9oDV1YQJAR6Q3}_}BXTFg$A$E!oGG%`Rc()-Ysc%w za(yEn0fw~AaEFr}Rxi;if?Gv)&g~21UzXU9osI9{rNfH$gPTTk#^B|irEc<8W+|9$ zc~R${X2)N!npz1DFVa%nEW)cgPq`MSs)_I*Xwo<+ZK-2^hD(Mc8rF1+2v7&qV;5SET-ygMLNFsb~#u+LpD$uLR1o!ha67gPV5Q{v#PZK5X zUT4aZ{o}&*q7rs)v%*fDTl%}VFX?Oi{i+oKVUBqbi8w#FI%_5;6`?(yc&(Fed4Quy8xsswG+o&R zO1#lUiA%!}61s3jR7;+iO$;1YN;_*yUnJK=$PT_}Q%&0T@2i$ zwGC@ZE^A62YeOS9DU9me5#`(wv24fK=C)N$>!!6V#6rX3xiHehfdvwWJ>_fwz9l)o`Vw9yi z0p5BgvIM5o_ zgo-xaAkS_mya8FXo1Ke4;U*7TGSfm0!fb4{E5Ar8T3p!Z@4;FYT8m=d`C@4-LM121 z?6W@9d@52vxUT-6K_;1!SE%FZHcm0U$SsC%QB zxkTrfH;#Y7OYPy!nt|k^Lgz}uYudos9wI^8x>Y{fTzv9gfTVXN2xH`;Er=rTeAO1x znaaJOR-I)qwD4z%&dDjY)@s`LLSd#FoD!?NY~9#wQRTHpD7Vyyq?tKUHKv6^VE93U zt_&ePH+LM-+9w-_9rvc|>B!oT>_L59nipM-@ITy|x=P%Ezu@Y?N!?jpwP%lm;0V5p z?-$)m84(|7vxV<6f%rK3!(R7>^!EuvA&j@jdTI+5S1E{(a*wvsV}_)HDR&8iuc#>+ zMr^2z*@GTnfDW-QS38OJPR3h6U&mA;vA6Pr)MoT7%NvA`%a&JPi|K8NP$b1QY#WdMt8-CDA zyL0UXNpZ?x=tj~LeM0wk<0Dlvn$rtjd$36`+mlf6;Q}K2{%?%EQ+#FJy6v5cS+Q-~ ztk||Iwr$(CZQHi38QZF;lFFBNt+mg2*V_AhzkM<8#>E_S^xj8%T5tXTytD6f)vePG z^B0Ne-*6Pqg+rVW?%FGHLhl^ycQM-dhNCr)tGC|XyES*NK%*4AnZ!V+Zu?x zV2a82fs8?o?X} zjC1`&uo1Ti*gaP@E43NageV^$Xue3%es2pOrLdgznZ!_a{*`tfA+vnUv;^Ebi3cc$?-kh76PqA zMpL!y(V=4BGPQSU)78q~N}_@xY5S>BavY3Sez-+%b*m0v*tOz6zub9%*~%-B)lb}t zy1UgzupFgf?XyMa+j}Yu>102tP$^S9f7;b7N&8?_lYG$okIC`h2QCT_)HxG1V4Uv{xdA4k3-FVY)d}`cmkePsLScG&~@wE?ix2<(G7h zQ7&jBQ}Kx9mm<0frw#BDYR7_HvY7En#z?&*FurzdDNdfF znCL1U3#iO`BnfPyM@>;#m2Lw9cGn;(5*QN9$zd4P68ji$X?^=qHraP~Nk@JX6}S>2 zhJz4MVTib`OlEAqt!UYobU0-0r*`=03)&q7ubQXrt|t?^U^Z#MEZV?VEin3Nv1~?U zuwwSeR10BrNZ@*h7M)aTxG`D(By$(ZP#UmBGf}duX zhx;7y1x@j2t5sS#QjbEPIj95hV8*7uF6c}~NBl5|hgbB(}M3vnt zu_^>@s*Bd>w;{6v53iF5q7Em>8n&m&MXL#ilSzuC6HTzzi-V#lWoX zBOSBYm|ti@bXb9HZ~}=dlV+F?nYo3?YaV2=N@AI5T5LWWZzwvnFa%w%C<$wBkc@&3 zyUE^8xu<=k!KX<}XJYo8L5NLySP)cF392GK97(ylPS+&b}$M$Y+1VDrJa`GG7+%ToAsh z5NEB9oVv>as?i7f^o>0XCd%2wIaNRyejlFws`bXG$Mhmb6S&shdZKo;p&~b4wv$ z?2ZoM$la+_?cynm&~jEi6bnD;zSx<0BuCSDHGSssT7Qctf`0U!GDwG=+^|-a5%8Ty z&Q!%m%geLjBT*#}t zv1wDzuC)_WK1E|H?NZ&-xr5OX(ukXMYM~_2c;K}219agkgBte_#f+b9Al8XjL-p}1 z8deBZFjplH85+Fa5Q$MbL>AfKPxj?6Bib2pevGxIGAG=vr;IuuC%sq9x{g4L$?Bw+ zvoo`E)3#bpJ{Ij>Yn0I>R&&5B$&M|r&zxh+q>*QPaxi2{lp?omkCo~7ibow#@{0P> z&XBocU8KAP3hNPKEMksQ^90zB1&&b1Me>?maT}4xv7QHA@Nbvt-iWy7+yPFa9G0DP zP82ooqy_ku{UPv$YF0kFrrx3L=FI|AjG7*(paRLM0k1J>3oPxU0Zd+4&vIMW>h4O5G zej2N$(e|2Re z@8xQ|uUvbA8QVXGjZ{Uiolxb7c7C^nW`P(m*Jkqn)qdI0xTa#fcK7SLp)<86(c`A3 zFNB4y#NHe$wYc7V)|=uiW8gS{1WMaJhDj4xYhld;zJip&uJ{Jg3R`n+jywDc*=>bW zEqw(_+j%8LMRrH~+M*$V$xn9x9P&zt^evq$P`aSf-51`ZOKm(35OEUMlO^$>%@b?a z>qXny!8eV7cI)cb0lu+dwzGH(Drx1-g+uDX;Oy$cs+gz~?LWif;#!+IvPR6fa&@Gj zwz!Vw9@-Jm1QtYT?I@JQf%`=$^I%0NK9CJ75gA}ff@?I*xUD7!x*qcyTX5X+pS zAVy4{51-dHKs*OroaTy;U?zpFS;bKV7wb}8v+Q#z<^$%NXN(_hG}*9E_DhrRd7Jqp zr}2jKH{avzrpXj?cW{17{kgKql+R(Ew55YiKK7=8nkzp7Sx<956tRa(|yvHlW zNO7|;GvR(1q}GrTY@uC&ow0me|8wE(PzOd}Y=T+Ih8@c2&~6(nzQrK??I7DbOguA9GUoz3ASU%BFCc8LBsslu|nl>q8Ag(jA9vkQ`q2amJ5FfA7GoCdsLW znuok(diRhuN+)A&`rH{$(HXWyG2TLXhVDo4xu?}k2cH7QsoS>sPV)ylb45Zt&_+1& zT)Yzh#FHRZ-z_Q^8~IZ+G~+qSw-D<{0NZ5!J1%rAc`B23T98TMh9ylkzdk^O?W`@C??Z5U9#vi0d<(`?9fQvNN^ji;&r}geU zSbKR5Mv$&u8d|iB^qiLaZQ#@)%kx1N;Og8Js>HQD3W4~pI(l>KiHpAv&-Ev45z(vYK<>p6 z6#pU(@rUu{i9UngMhU&FI5yeRub4#u=9H+N>L@t}djC(Schr;gc90n%)qH{$l0L4T z;=R%r>CuxH!O@+eBR`rBLrT0vnP^sJ^+qE^C8ZY0-@te3SjnJ)d(~HcnQw@`|qAp|Trrs^E*n zY1!(LgVJfL?@N+u{*!Q97N{Uu)ZvaN>hsM~J?*Qvqv;sLnXHjKrtG&x)7tk?8%AHI zo5eI#`qV1{HmUf-Fucg1xn?Kw;(!%pdQ)ai43J3NP4{%x1D zI0#GZh8tjRy+2{m$HyI(iEwK30a4I36cSht3MM85UqccyUq6$j5K>|w$O3>`Ds;`0736+M@q(9$(`C6QZQ-vAKjIXKR(NAH88 zwfM6_nGWlhpy!_o56^BU``%TQ%tD4hs2^<2pLypjAZ;W9xAQRfF_;T9W-uidv{`B z{)0udL1~tMg}a!hzVM0a_$RbuQk|EG&(z*{nZXD3hf;BJe4YxX8pKX7VaIjjDP%sk zU5iOkhzZ&%?A@YfaJ8l&H;it@;u>AIB`TkglVuy>h;vjtq~o`5NfvR!ZfL8qS#LL` zD!nYHGzZ|}BcCf8s>b=5nZRYV{)KK#7$I06s<;RyYC3<~`mob_t2IfR*dkFJyL?FU zvuo-EE4U(-le)zdgtW#AVA~zjx*^80kd3A#?vI63pLnW2{j*=#UG}ISD>=ZGA$H&` z?Nd8&11*4`%MQlM64wfK`{O*ad5}vk4{Gy}F98xIAsmjp*9P=a^yBHBjF2*Iibo2H zGJAMFDjZcVd%6bZ`dz;I@F55VCn{~RKUqD#V_d{gc|Z|`RstPw$>Wu+;SY%yf1rI=>51Oolm>cnjOWHm?ydcgGs_kPUu=?ZKtQS> zKtLS-v$OMWXO>B%Z4LFUgw4MqA?60o{}-^6tf(c0{Y3|yF##+)RoXYVY-lyPhgn{1 z>}yF0Ab}D#1*746QAj5c%66>7CCWs8O7_d&=Ktu!SK(m}StvvBT1$8QP3O2a*^BNA z)HPhmIi*((2`?w}IE6Fo-SwzI_F~OC7OR}guyY!bOQfpNRg3iMvsFPYb9-;dT6T%R zhLwIjgiE^-9_4F3eMHZ3LI%bbOmWVe{SONpujQ;3C+58=Be4@yJK>3&@O>YaSdrevAdCLMe_tL zl8@F}{Oc!aXO5!t!|`I zdC`k$5z9Yf%RYJp2|k*DK1W@AN23W%SD0EdUV^6~6bPp_HZi0@dku_^N--oZv}wZA zH?Bf`knx%oKB36^L;P%|pf#}Tp(icw=0(2N4aL_Ea=9DMtF})2ay68V{*KfE{O=xL zf}tcfCL|D$6g&_R;r~1m{+)sutQPKzVv6Zw(%8w&4aeiy(qct1x38kiqgk!0^^X3IzI2ia zxI|Q)qJNEf{=I$RnS0`SGMVg~>kHQB@~&iT7+eR!Ilo1ZrDc3TVW)CvFFjHK4K}Kh z)dxbw7X%-9Ol&Y4NQE~bX6z+BGOEIIfJ~KfD}f4spk(m62#u%k<+iD^`AqIhWxtKGIm)l$7=L`=VU0Bz3-cLvy&xdHDe-_d3%*C|Q&&_-n;B`87X zDBt3O?Wo-Hg6*i?f`G}5zvM?OzQjkB8uJhzj3N;TM5dSM$C@~gGU7nt-XX_W(p0IA6$~^cP*IAnA<=@HVqNz=Dp#Rcj9_6*8o|*^YseK_4d&mBY*Y&q z8gtl;(5%~3Ehpz)bLX%)7|h4tAwx}1+8CBtu9f5%^SE<&4%~9EVn4*_!r}+{^2;} zwz}#@Iw?&|8F2LdXUIjh@kg3QH69tqxR_FzA;zVpY=E zcHnWh(3j3UXeD=4m_@)Ea4m#r?axC&X%#wC8FpJPDYR~@65T?pXuWdPzEqXP>|L`S zKYFF0I~%I>SFWF|&sDsRdXf$-TVGSoWTx7>7mtCVUrQNVjZ#;Krobgh76tiP*0(5A zs#<7EJ#J`Xhp*IXB+p5{b&X3GXi#b*u~peAD9vr0*Vd&mvMY^zxTD=e(`}ybDt=BC(4q)CIdp>aK z0c?i@vFWjcbK>oH&V_1m_EuZ;KjZSiW^i30U` zGLK{%1o9TGm8@gy+Rl=-5&z`~Un@l*2ne3e9B+>wKyxuoUa1qhf?-Pi= zZLCD-b7*(ybv6uh4b`s&Ol3hX2ZE<}N@iC+h&{J5U|U{u$XK0AJz)!TSX6lrkG?ris;y{s zv`B5Rq(~G58?KlDZ!o9q5t%^E4`+=ku_h@~w**@jHV-+cBW-`H9HS@o?YUUkKJ;AeCMz^f@FgrRi@?NvO3|J zBM^>4Z}}!vzNum!R~o0)rszHG(eeq!#C^wggTgne^2xc9nIanR$pH1*O;V>3&#PNa z7yoo?%T(?m-x_ow+M0Bk!@ow>A=skt&~xK=a(GEGIWo4AW09{U%(;CYLiQIY$bl3M zxC_FGKY%J`&oTS{R8MHVe{vghGEshWi!(EK*DWmoOv|(Ff#(bZ-<~{rc|a%}Q4-;w z{2gca97m~Nj@Nl{d)P`J__#Zgvc@)q_(yfrF2yHs6RU8UXxcU(T257}E#E_A}%2_IW?%O+7v((|iQ{H<|$S7w?;7J;iwD>xbZc$=l*(bzRXc~edIirlU0T&0E_EXfS5%yA zs0y|Sp&i`0zf;VLN=%hmo9!aoLGP<*Z7E8GT}%)cLFs(KHScNBco(uTubbxCOD_%P zD7XlHivrSWLth7jf4QR9`jFNk-7i%v4*4fC*A=;$Dm@Z^OK|rAw>*CI%E z3%14h-)|Q%_$wi9=p!;+cQ*N1(47<49TyB&B*bm_m$rs+*ztWStR~>b zE@V06;x19Y_A85N;R+?e?zMTIqdB1R8>(!4_S!Fh={DGqYvA0e-P~2DaRpCYf4$-Q z*&}6D!N_@s`$W(|!DOv%>R0n;?#(HgaI$KpHYpnbj~I5eeI(u4CS7OJajF%iKz)*V zt@8=9)tD1ML_CrdXQ81bETBeW!IEy7mu4*bnU--kK;KfgZ>oO>f)Sz~UK1AW#ZQ_ic&!ce~@(m2HT@xEh5u%{t}EOn8ET#*U~PfiIh2QgpT z%gJU6!sR2rA94u@xj3%Q`n@d}^iMH#X>&Bax+f4cG7E{g{vlJQ!f9T5wA6T`CgB%6 z-9aRjn$BmH=)}?xWm9bf`Yj-f;%XKRp@&7?L^k?OT_oZXASIqbQ#eztkW=tmRF$~% z6(&9wJuC-BlGrR*(LQKx8}jaE5t`aaz#Xb;(TBK98RJBjiqbZFyRNTOPA;fG$;~e` zsd6SBii3^(1Y`6^#>kJ77xF{PAfDkyevgox`qW`nz1F`&w*DH5Oh1idOTLES>DToi z8Qs4|?%#%>yuQO1#{R!-+2AOFznWo)e3~_D!nhoDgjovB%A8< zt%c^KlBL$cDPu!Cc`NLc_8>f?)!FGV7yudL$bKj!h;eOGkd;P~sr6>r6TlO{Wp1%xep8r1W{`<4am^(U} z+nCDP{Z*I?IGBE&*KjiaR}dpvM{ZFMW%P5Ft)u$FD373r2|cNsz%b0uk1T+mQI@4& zFF*~xDxDRew1Bol-*q>F{Xw8BUO;>|0KXf`lv7IUh%GgeLUzR|_r(TXZTbfXFE0oc zmGMwzNFgkdg><=+3MnncRD^O`m=SxJ6?}NZ8BR)=ag^b4Eiu<_bN&i0wUaCGi60W6 z%iMl&`h8G)y`gfrVw$={cZ)H4KSQO`UV#!@@cDx*hChXJB7zY18EsIo1)tw0k+8u; zg(6qLysbxVbLFbkYqKbEuc3KxTE+%j5&k>zHB8_FuDcOO3}FS|eTxoUh2~|Bh?pD| zsmg(EtMh`@s;`(r!%^xxDt(5wawK+*jLl>_Z3shaB~vdkJ!V3RnShluzmwn7>PHai z3avc`)jZSAvTVC6{2~^CaX49GXMtd|sbi*swkgoyLr=&yp!ASd^mIC^D;a|<=3pSt zM&0u%#%DGzlF4JpMDs~#kU;UCtyW+d3JwNiu`Uc7Yi6%2gfvP_pz8I{Q<#25DjM_D z(>8yI^s@_tG@c=cPoZImW1CO~`>l>rs=i4BFMZT`vq5bMOe!H@8q@sEZX<-kiY&@u3g1YFc zc@)@OF;K-JjI(eLs~hy8qOa9H1zb!3GslI!nH2DhP=p*NLHeh^9WF?4Iakt+b( z-4!;Q-8c|AX>t+5I64EKpDj4l2x*!_REy9L_9F~i{)1?o#Ws{YG#*}lg_zktt#ZlN zmoNsGm7$AXLink`GWtY*TZEH!J9Qv+A1y|@>?&(pb(6XW#ZF*}x*{60%wnt{n8Icp zq-Kb($kh6v_voqvA`8rq!cgyu;GaWZ>C2t6G5wk! zcKTlw=>KX3ldU}a1%XESW71))Z=HW%sMj2znJ;fdN${00DGGO}d+QsTQ=f;BeZ`eC~0-*|gn$9G#`#0YbT(>O(k&!?2jI z&oi9&3n6Vz<4RGR}h*1ggr#&0f%Op(6{h>EEVFNJ0C>I~~SmvqG+{RXDrexBz zw;bR@$Wi`HQ3e*eU@Cr-4Z7g`1R}>3-Qej(#Dmy|CuFc{Pg83Jv(pOMs$t(9vVJQJ zXqn2Ol^MW;DXq!qM$55vZ{JRqg!Q1^Qdn&FIug%O3=PUr~Q`UJuZ zc`_bE6i^Cp_(fka&A)MsPukiMyjG$((zE$!u>wyAe`gf-1Qf}WFfi1Y{^ zdCTTrxqpQE#2BYWEBnTr)u-qGSVRMV7HTC(x zb(0FjYH~nW07F|{@oy)rlK6CCCgyX?cB;19Z(bCP5>lwN0UBF}Ia|L0$oGHl-oSTZ zr;(u7nDjSA03v~XoF@ULya8|dzH<2G=n9A)AIkQKF0mn?!BU(ipengAE}6r`CE!jd z=EcX8exgDZZQ~~fgxR-2yF;l|kAfnjhz|i_o~cYRdhnE~1yZ{s zG!kZJ<-OVnO{s3bOJK<)`O;rk>=^Sj3M76Nqkj<_@Jjw~iOkWUCL+*Z?+_Jvdb!0cUBy=(5W9H-r4I zxAFts>~r)B>KXdQANyaeKvFheZMgoq4EVV0|^NR@>ea* zh%<78{}wsdL|9N1!jCN-)wH4SDhl$MN^f_3&qo?>Bz#?c{ne*P1+1 z!a`(2Bxy`S^(cw^dv{$cT^wEQ5;+MBctgPfM9kIQGFUKI#>ZfW9(8~Ey-8`OR_XoT zflW^mFO?AwFWx9mW2-@LrY~I1{dlX~jBMt!3?5goHeg#o0lKgQ+eZcIheq@A&dD}GY&1c%hsgo?z zH>-hNgF?Jk*F0UOZ*bs+MXO(dLZ|jzKu5xV1v#!RD+jRrHdQ z>>b){U(I@i6~4kZXn$rk?8j(eVKYJ2&k7Uc`u01>B&G@c`P#t#x@>Q$N$1aT514fK zA_H8j)UKen{k^ehe%nbTw}<JV6xN_|| z(bd-%aL}b z3VITE`N~@WlS+cV>C9TU;YfsU3;`+@hJSbG6aGvis{Gs%2K|($)(_VfpHB|DG8Nje+0tCNW%_cu3hk0F)~{-% zW{2xSu@)Xnc`Dc%AOH)+LT97ImFR*WekSnJ3OYIs#ijP4TD`K&7NZKsfZ;76k@VD3py?pSw~~r^VV$Z zuUl9lF4H2(Qga0EP_==vQ@f!FLC+Y74*s`Ogq|^!?RRt&9e9A&?Tdu=8SOva$dqgYU$zkKD3m>I=`nhx-+M;-leZgt z8TeyQFy`jtUg4Ih^JCUcq+g_qs?LXSxF#t+?1Jsr8c1PB#V+f6aOx@;ThTIR4AyF5 z3m$Rq(6R}U2S}~Bn^M0P&Aaux%D@ijl0kCCF48t)+Y`u>g?|ibOAJoQGML@;tn{%3IEMaD(@`{7ByXQ`PmDeK*;W?| zI8%%P8%9)9{9DL-zKbDQ*%@Cl>Q)_M6vCs~5rb(oTD%vH@o?Gk?UoRD=C-M|w~&vb z{n-B9>t0EORXd-VfYC>sNv5vOF_Wo5V)(Oa%<~f|EU7=npanpVX^SxPW;C!hMf#kq z*vGNI-!9&y!|>Zj0V<~)zDu=JqlQu+ii387D-_U>WI_`3pDuHg{%N5yzU zEulPN)%3&{PX|hv*rc&NKe(bJLhH=GPuLk5pSo9J(M9J3v)FxCo65T%9x<)x+&4Rr2#nu2?~Glz|{28OV6 z)H^`XkUL|MG-$XE=M4*fIPmeR2wFWd>5o*)(gG^Y>!P4(f z68RkX0cRBOFc@`W-IA(q@p@m>*2q-`LfujOJ8-h$OgHte;KY4vZKTxO95;wh#2ZDL zKi8aHkz2l54lZd81t`yY$Tq_Q2_JZ1d(65apMg}vqwx=ceNOWjFB)6m3Q!edw2<{O z4J6+Un(E8jxs-L-K_XM_VWahy zE+9fm_ZaxjNi{fI_AqLKqhc4IkqQ4`Ut$=0L)nzlQw^%i?bP~znsbMY3f}*nPWqQZ zz_CQDpZ?Npn_pEr`~SX1`OoSkS;bmzQ69y|W_4bH3&U3F7EBlx+t%2R02VRJ01cfX zo$$^ObDHK%bHQaOcMpCq@@Jp8!OLYVQO+itW1ZxlkmoG#3FmD4b61mZjn4H|pSmYi2YE;I#@jtq8Mhjdgl!6({gUsQA>IRXb#AyWVt7b=(HWGUj;wd!S+q z4S+H|y<$yPrrrTqQHsa}H`#eJFV2H5Dd2FqFMA%mwd`4hMK4722|78d(XV}rz^-GV(k zqsQ>JWy~cg_hbp0=~V3&TnniMQ}t#INg!o2lN#H4_gx8Tn~Gu&*ZF8#kkM*5gvPu^ zw?!M^05{7q&uthxOn?%#%RA_%y~1IWly7&_-sV!D=Kw3DP+W)>YYRiAqw^d7vG_Q%v;tRbE1pOBHc)c&_5=@wo4CJTJ1DeZErEvP5J(kc^GnGYX z|LqQjTkM{^gO2cO#-(g!7^di@$J0ibC(vsnVkHt3osnWL8?-;R1BW40q5Tmu_9L-s z7fNF5fiuS-%B%F$;D97N-I@!~c+J>nv%mzQ5vs?1MgR@XD*Gv`A{s8 z5Cr>z5j?|sb>n=c*xSKHpdy667QZT?$j^Doa%#m4ggM@4t5Oe%iW z@w~j_B>GJJkO+6dVHD#CkbC(=VMN8nDkz%44SK62N(ZM#AsNz1KW~3(i=)O;q5JrK z?vAVuL}Rme)OGQuLn8{3+V352UvEBV^>|-TAAa1l-T)oiYYD&}Kyxw73shz?Bn})7 z_a_CIPYK(zMp(i+tRLjy4dV#CBf3s@bdmwXo`Y)dRq9r9-c@^2S*YoNOmAX%@OYJOXs zT*->in!8Ca_$W8zMBb04@|Y)|>WZ)-QGO&S7Zga1(1#VR&)X+MD{LEPc%EJCXIMtr z1X@}oNU;_(dfQ_|kI-iUSTKiVzcy+zr72kq)TIp(GkgVyd%{8@^)$%G)pA@^Mfj71FG%d?sf(2Vm>k%X^RS`}v0LmwIQ7!_7cy$Q8pT?X1VWecA_W68u==HbrU& z@&L6pM0@8ZHL?k{6+&ewAj%grb6y@0$3oamTvXsjGmPL_$~OpIyIq%b$(uI1VKo zk_@{r>1p84UK3}B>@d?xUZ}dJk>uEd+-QhwFQ`U?rA=jj+$w8sD#{492P}~R#%z%0 z5dlltiAaiPKv9fhjmuy{*m!C22$;>#85EduvdSrFES{QO$bHpa7E@&{bWb@<7VhTF zXCFS_wB>7*MjJ3$_i4^A2XfF2t7`LOr3B@??OOUk=4fKkaHne4RhI~Lm$JrHfUU*h zgD9G66;_F?3>0W{pW2A^DR7Bq`ZUiSc${S8EM>%gFIqAw0du4~kU#vuCb=$I_PQv? zZfEY7X6c{jJZ@nF&T>4oyy(Zr_XqnMq)ZtGPASbr?IhZOnL|JKY()`eo=P5UK9(P-@ zOJKFogtk|pscVD+#$7KZs^K5l4gC}*CTd0neZ8L(^&1*bPrCp23%{VNp`4Ld*)Fly z)b|zb*bCzp?&X3_=qLT&0J+=p01&}9*xbk~^hd^@mV!Ha`1H+M&60QH2c|!Ty`RepK|H|Moc5MquD z=&$Ne3%WX+|7?iiR8=7*LW9O3{O%Z6U6`VekeF8lGr5vd)rsZu@X#5!^G1;nV60cz zW?9%HgD}1G{E(YvcLcIMQR65BP50)a;WI*tjRzL7diqRqh$3>OK{06VyC=pj6OiardshTnYfve5U>Tln@y{DC99f!B4> zCrZa$B;IjDrg}*D5l=CrW|wdzENw{q?oIj!Px^7DnqAsU7_=AzXxoA;4(YvN5^9ag zwEd4-HOlO~R0~zk>!4|_Z&&q}agLD`Nx!%9RLC#7fK=w06e zOK<>|#@|e2zjwZ5aB>DJ%#P>k4s0+xHJs@jROvoDQfSoE84l8{9y%5^POiP+?yq0> z7+Ymbld(s-4p5vykK@g<{X*!DZt1QWXKGmj${`@_R~=a!qPzB357nWW^KmhV!^G3i zsYN{2_@gtzsZH*FY!}}vNDnqq>kc(+7wK}M4V*O!M&GQ|uj>+8!Q8Ja+j3f*MzwcI z^s4FXGC=LZ?il4D+Y^f89wh!d7EU-5dZ}}>_PO}jXRQ@q^CjK-{KVnmFd_f&IDKmx zZ5;PDLF%_O);<4t`WSMN;Ec^;I#wU?Z?_R|Jg`#wbq;UM#50f@7F?b7ySi-$C-N;% zqXowTcT@=|@~*a)dkZ836R=H+m6|fynm#0Y{KVyYU=_*NHO1{=Eo{^L@wWr7 zjz9GOu8Fd&v}a4d+}@J^9=!dJRsCO@=>K6UCM)Xv6};tb)M#{(k!i}_0Rjq z2kb7wPcNgov%%q#(1cLykjrxAg)By+3QueBR>Wsep&rWQHq1wE!JP+L;q+mXts{j@ zOY@t9BFmofApO0k@iBFPeKsV3X=|=_t65QyohXMSfMRr7Jyf8~ogPVmJwbr@`nmml zov*NCf;*mT(5s4K=~xtYy8SzE66W#tW4X#RnN%<8FGCT{z#jRKy@Cy|!yR`7dsJ}R z!eZzPCF+^b0qwg(mE=M#V;Ud9)2QL~ z-r-2%0dbya)%ui_>e6>O3-}4+Q!D+MU-9HL2tH)O`cMC1^=rA=q$Pcc;Zel@@ss|K zH*WMdS^O`5Uv1qNTMhM(=;qjhaJ|ZC41i2!kt4;JGlXQ$tvvF8Oa^C@(q6(&6B^l) zNG{GaX?`qROHwL-F1WZDEF;C6Inuv~1&ZuP3j53547P38tr|iPH#3&hN*g0R^H;#) znft`cw0+^Lwe{!^kQat+xjf_$SZ05OD6~U`6njelvd+4pLZU(0ykS5&S$)u?gm!;} z+gJ8g12b1D4^2HH!?AHFAjDAP^q)Juw|hZfIv{3Ryn%4B^-rqIF2 zeWk^za4fq#@;re{z4_O|Zj&Zn{2WsyI^1%NW=2qA^iMH>u>@;GAYI>Bk~u0wWQrz* zdEf)7_pSYMg;_9^qrCzvv{FZYwgXK}6e6ceOH+i&+O=x&{7aRI(oz3NHc;UAxMJE2 zDb0QeNpm$TDcshGWs!Zy!shR$lC_Yh-PkQ`{V~z!AvUoRr&BAGS#_*ZygwI2-)6+a zq|?A;+-7f0Dk4uuht z6sWPGl&Q$bev1b6%aheld88yMmBp2j=z*egn1aAWd?zN=yEtRDGRW&nmv#%OQwuJ; zqKZ`L4DsqJwU{&2V9f>2`1QP7U}`6)$qxTNEi`4xn!HzIY?hDnnJZw+mFnVSry=bLH7ar+M(e9h?GiwnOM?9ZJcTJ08)T1-+J#cr&uHhXkiJ~}&(}wvzCo33 zLd_<%rRFQ3d5fzKYQy41<`HKk#$yn$Q+Fx-?{3h72XZrr*uN!5QjRon-qZh9-uZ$rWEKZ z!dJMP`hprNS{pzqO`Qhx`oXGd{4Uy0&RDwJ`hqLw4v5k#MOjvyt}IkLW{nNau8~XM z&XKeoVYreO=$E%z^WMd>J%tCdJx5-h+8tiawu2;s& zD7l`HV!v@vcX*qM(}KvZ#%0VBIbd)NClLBu-m2Scx1H`jyLYce;2z;;eo;ckYlU53 z9JcQS+CvCwj*yxM+e*1Vk6}+qIik2VzvUuJyWyO}piM1rEk%IvS;dsXOIR!#9S;G@ zPcz^%QTf9D<2~VA5L@Z@FGQqwyx~Mc-QFzT4Em?7u`OU!PB=MD8jx%J{<`tH$Kcxz zjIvb$x|`s!-^^Zw{hGV>rg&zb;=m?XYAU0LFw+uyp8v@Y)zmjj&Ib7Y1@r4`cfrS%cVxJiw`;*BwIU*6QVsBBL;~nw4`ZFqs z1YSgLVy=rvA&GQB4MDG+j^)X1N=T;Ty2lE-`zrg(dNq?=Q`nCM*o8~A2V~UPArX<| zF;e$5B0hPSo56=ePVy{nah#?e-Yi3g*z6iYJ#BFJ-5f0KlQ-PRiuGwe29fyk1T6>& zeo2lvb%h9Vzi&^QcVNp}J!x&ubtw5fKa|n2XSMlg#=G*6F|;p)%SpN~l8BaMREDQN z-c9O}?%U1p-ej%hzIDB!W_{`9lS}_U==fdYpAil1E3MQOFW^u#B)Cs zTE3|YB0bKpXuDKR9z&{4gNO3VHDLB!xxPES+)yaJxo<|}&bl`F21};xsQnc!*FPZA zSct2IU3gEu@WQKmY-vA5>MV?7W|{$rAEj4<8`*i)<%fj*gDz2=ApqZ&MP&0UmO1?q!GN=di+n(#bB_mHa z(H-rIOJqamMfwB%?di!TrN=x~0jOJtvb0e9uu$ZCVj(gJyK}Fa5F2S?VE30P{#n3eMy!-v7e8viCooW9cfQx%xyPNL*eDKL zB=X@jxulpkLfnar7D2EeP*0L7c9urDz{XdV;@tO;u`7DlN7#~ zAKA~uM2u8_<5FLkd}OzD9K zO5&hbK8yakUXn8r*H9RE zO9Gsipa2()=&x=1mnQtNP#4m%GXThu8Ccqx*qb;S{5}>bU*V5{SY~(Hb={cyTeaTM zMEaKedtJf^NnJrwQ^Bd57vSlJ3l@$^0QpX@_1>h^+js8QVpwOiIMOiSC_>3@dt*&| zV?0jRdlgn|FIYam0s)a@5?0kf7A|GD|dRnP1=B!{ldr;N5s)}MJ=i4XEqlC}w)LEJ}7f9~c!?It(s zu>b=YBlFRi(H-%8A!@Vr{mndRJ z_jx*?BQpK>qh`2+3cBJhx;>yXPjv>dQ0m+nd4nl(L;GmF-?XzlMK zP(Xeyh7mFlP#=J%i~L{o)*sG7H5g~bnL2Hn3y!!r5YiYRzgNTvgL<(*g5IB*gcajK z86X3LoW*5heFmkIQ-I_@I_7b!Xq#O;IzOv(TK#(4gd)rmCbv5YfA4koRfLydaIXUU z8(q?)EWy!sjsn-oyUC&uwJqEXdlM}#tmD~*Ztav=mTQyrw0^F=1I5lj*}GSQTQOW{ z=O12;?fJfXxy`)ItiDB@0sk43AZo_sRn*jc#S|(2*%tH84d|UTYN!O4R(G6-CM}84 zpiyYJ^wl|w@!*t)dwn0XJv2kuHgbfNL$U6)O-k*~7pQ?y=sQJdKk5x`1>PEAxjIWn z{H$)fZH4S}%?xzAy1om0^`Q$^?QEL}*ZVQK)NLgmnJ`(we z21c23X1&=^>k;UF-}7}@nzUf5HSLUcOYW&gsqUrj7%d$)+d8ZWwTZq)tOgc%fz95+ zl%sdl)|l|jXfqIcjKTFrX74Rbq1}osA~fXPSPE?XO=__@`7k4Taa!sHE8v-zfx(AM zXT_(7u;&_?4ZIh%45x>p!(I&xV|IE**qbqCRGD5aqLpCRvrNy@uT?iYo-FPpu`t}J zSTZ}MDrud+`#^14r`A%UoMvN;raizytxMBV$~~y3i0#m}0F}Dj_fBIz+)1RWdnctP z>^O^vd0E+jS+$V~*`mZWER~L^q?i-6RPxxufWdrW=%prbCYT{5>Vgu%vPB)~NN*2L zB?xQg2K@+Xy=sPh$%10LH!39p&SJG+3^i*lFLn=uY8Io6AXRZf;p~v@1(hWsFzeKzx99_{w>r;cypkPVJCKtLGK>?-K0GE zGH>$g?u`)U_%0|f#!;+E>?v>qghuBwYZxZ*Q*EE|P|__G+OzC-Z+}CS(XK^t!TMoT zc+QU|1C_PGiVp&_^wMxfmMAuJDQ%1p4O|x5DljN6+MJiO%8s{^ts8$uh5`N~qK46c`3WY#hRH$QI@*i1OB7qBIN*S2gK#uVd{ zik+wwQ{D)g{XTGjKV1m#kYhmK#?uy)g@idi&^8mX)Ms`^=hQGY)j|LuFr8SJGZjr| zzZf{hxYg)-I^G|*#dT9Jj)+wMfz-l7ixjmwHK9L4aPdXyD-QCW!2|Jn(<3$pq-BM; zs(6}egHAL?8l?f}2FJSkP`N%hdAeBiD{3qVlghzJe5s9ZUMd`;KURm_eFaK?d&+TyC88v zCv2R(Qg~0VS?+p+l1e(aVq`($>|0b{{tPNbi} zaZDffTZ7N|t2D5DBv~aX#X+yGagWs1JRsqbr4L8a`B`m) z1p9?T`|*8ZXHS7YD8{P1Dk`EGM`2Yjsy0=7M&U6^VO30`Gx!ZkUoqmc3oUbd&)V*iD08>dk=#G!*cs~^tOw^s8YQqYJ z!5=-4ZB7rW4mQF&YZw>T_in-c9`0NqQ_5Q}fq|)%HECgBd5KIo`miEcJ>~a1e2B@) zL_rqoQ;1MowD34e6#_U+>D`WcnG5<2Q6cnt4Iv@NC$*M+i3!c?6hqPJLsB|SJ~xo! zm>!N;b0E{RX{d*in3&0w!cmB&TBNEjhxdg!fo+}iGE*BWV%x*46rT@+cXU;leofWy zxst{S8m!_#hIhbV7wfWN#th8OI5EUr3IR_GOIzBgGW1u4J*TQxtT7PXp#U#EagTV* zehVkBFF06`@5bh!t%L)-)`p|d7D|^kED7fsht#SN7*3`MKZX};Jh0~nCREL_BGqNR zxpJ4`V{%>CAqEE#Dt95u=;Un8wLhrac$fao`XlNsOH%&Ey2tK&vAcriS1kXnntDuttcN{%YJz@!$T zD&v6ZQ>zS1`o!qT=JK-Y+^i~bZkVJpN8%<4>HbuG($h9LP;{3DJF_Jcl8CA5M~<3s^!$Sg62zLEnJtZ z0`)jwK75Il6)9XLf(64~`778D6-#Ie1IR2Ffu+_Oty%$8u+bP$?803V5W6%(+iZzp zp5<&sBV&%CJcXUIATUakP1czt$&0x$lyoLH!ueNaIpvtO z*eCijxOv^-D?JaLzH<3yhOfDENi@q#4w(#tl-19(&Yc2K%S8Y&r{3~-)P17sC1{rQ zOy>IZ6%814_UoEi+w9a4XyGXF66{rgE~UT)oT4x zg9oIx@|{KL#VpTyE=6WK@Sbd9RKEEY)5W{-%0F^6(QMuT$RQRZ&yqfyF*Z$f8>{iT zq(;UzB-Ltv;VHvh4y%YvG^UEkvpe9ugiT97ErbY0ErCEOWs4J=kflA!*Q}gMbEP`N zY#L`x9a?E)*~B~t+7c8eR}VY`t}J;EWuJ-6&}SHnNZ8i0PZT^ahA@@HXk?c0{)6rC zP}I}_KK7MjXqn1E19gOwWvJ3i9>FNxN67o?lZy4H?n}%j|Dq$p%TFLUPJBD;R|*0O z3pLw^?*$9Ax!xy<&fO@;E2w$9nMez{5JdFO^q)B0OmGwkxxaDsEU+5C#g+?Ln-Vg@ z-=z4O*#*VJa*nujGnGfK#?`a|xfZsuiO+R}7y(d60@!WUIEUt>K+KTI&I z9YQ6#hVCo}0^*>yr-#Lisq6R?uI=Ms!J7}qm@B}Zu zp%f-~1Cf!-5S0xXl`oqq&fS=tt0`%dDWI&6pW(s zJXtYiY&~t>k5I0RK3sN;#8?#xO+*FeK#=C^%{Y>{k{~bXz%(H;)V5)DZRk~(_d0b6 zV!x54fwkl`1y;%U;n|E#^Vx(RGnuN|T$oJ^R%ZmI{8(9>U-K^QpDcT?Bb@|J0NAfvHtL#wP ziYupr2E5=_KS{U@;kyW7oy*+UTOiF*e+EhYqVcV^wx~5}49tBNSUHLH1=x}6L2Fl^4X4633$k!ZHZTL50Vq+a5+ z<}uglXQ<{x&6ey)-lq6;4KLHbR)_;Oo^FodsYSw3M-)FbLaBcPI=-ao+|))T2ksKb z{c%Fu`HR1dqNw8%>e0>HI2E_zNH1$+4RWfk}p-h(W@)7LC zwVnUO17y+~kw35CxVtokT44iF$l8XxYuetp)1Br${@lb(Q^e|q*5%7JNxp5B{r<09 z-~8o#rI1(Qb9FhW-igcsC6npf5j`-v!nCrAcVx5+S&_V2D>MOWp6cV$~Olhp2`F^Td{WV`2k4J`djb#M>5D#k&5XkMu*FiO(uP{SNX@(=)|Wm`@b> z_D<~{ip6@uyd7e3Rn+qM80@}Cl35~^)7XN?D{=B-4@gO4mY%`z!kMIZizhGtCH-*7 z{a%uB4usaUoJwbkVVj%8o!K^>W=(ZzRDA&kISY?`^0YHKe!()(*w@{w7o5lHd3(Us zUm-K=z&rEbOe$ackQ3XH=An;Qyug2g&vqf;zsRBldxA+=vNGoM$Zo9yT?Bn?`Hkiq z&h@Ss--~+=YOe@~JlC`CdSHy zcO`;bgMASYi6`WSw#Z|A;wQgH@>+I3OT6(*JgZZ_XQ!LrBJfVW2RK%#02|@V|H4&8DqslU6Zj(x!tM{h zRawG+Vy63_8gP#G!Eq>qKf(C&!^G$01~baLLk#)ov-Pqx~Du>%LHMv?=WBx2p2eV zbj5fjTBhwo&zeD=l1*o}Zs%SMxEi9yokhbHhY4N!XV?t8}?!?42E-B^Rh&ABFxovs*HeQ5{{*)SrnJ%e{){Z_#JH+jvwF7>Jo zE+qzWrugBwVOZou~oFa(wc7?`wNde>~HcC@>fA^o>ll?~aj-e|Ju z+iJzZg0y1@eQ4}rm`+@hH(|=gW^;>n>ydn!8%B4t7WL)R-D>mMw<7Wz6>ulFnM7QA ze2HEqaE4O6jpVq&ol3O$46r+DW@%glD8Kp*tFY#8oiSyMi#yEpVIw3#t?pXG?+H>v z$pUwT@0ri)_Bt+H(^uzp6qx!P(AdAI_Q?b`>0J?aAKTPt>73uL2(WXws9+T|%U)Jq zP?Oy;y6?{%J>}?ZmfcnyIQHh_jL;oD$`U#!v@Bf{5%^F`UiOX%)<0DqQ^nqA5Ac!< z1DPO5C>W0%m?MN*x(k>lDT4W3;tPi=&yM#Wjwc5IFNiLkQf`7GN+J*MbB4q~HVePM zeDj8YyA*btY&n!M9$tuOxG0)2um))hsVsY+(p~JnDaT7x(s2If0H_iRSju7!z7p|8 zzI`NV!1hHWX3m)?t68k6yNKvop{Z>kl)f5GV(~1InT4%9IxqhDX-rgj)Y|NYq_NTlZgz-)=Y$=x9L7|k0=m@6WQ<4&r=BX@pW25NtCI+N{e&`RGSpR zeb^`@FHm5?pWseZ6V08{R(ki}--13S2op~9Kzz;#cPgL}Tmrqd+gs(fJLTCM8#&|S z^L+7PbAhltJDyyxAVxqf(2h!RGC3$;hX@YNz@&JRw!m5?Q)|-tZ8u0D$4we+QytG^ zj0U_@+N|OJlBHdWPN!K={a$R1Zi{2%5QD}s&s-Xn1tY1cwh)8VW z$pjq>8sj4)?76EJs6bA0E&pfr^Vq`&Xc;Tl2T!fm+MV%!H|i0o;7A=zE?dl)-Iz#P zSY7QRV`qRc6b&rON`BValC01zSLQpVemH5y%FxK8m^PeNN(Hf1(%C}KPfC*L?Nm!nMW0@J3(J=mYq3DPk;TMs%h`-amWbc%7{1Lg3$ z^e=btuqch-lydbtLvazh+fx?87Q7!YRT(=-Vx;hO)?o@f1($e5B?JB9jcRd;zM;iE zu?3EqyK`@_5Smr#^a`C#M>sRwq2^|ym)X*r;0v6AM`Zz1aK94@9Ti)Lixun2N!e-A z>w#}xPxVd9AfaF$XTTff?+#D(xwOpjZj9-&SU%7Z-E2-VF-n#xnPeQH*67J=j>TL# z<v}>AiTXrQ(fYa%82%qlH=L z6Fg8@r4p+BeTZ!5cZlu$iR?EJpYuTx>cJ~{{B7KODY#o*2seq=p2U0Rh;3mX^9sza zk^R_l7jzL5BXWlrVkhh!+LQ-Nc0I`6l1mWkp~inn)HQWqMTWl4G-TBLglR~n&6J?4 z7J)IO{wkrtT!Csntw3H$Mnj>@;QbrxC&Shqn^VVu$Ls*_c~TTY~fri6fO-=eJsC*8(3(H zSyO>=B;G`qA398OvCHRvf3mabrPZaaLhn*+jeA`qI!gP&i8Zs!*bBqMXDJpSZG$N) zx0rDLvcO>EoqCTR)|n7eOp-jmd>`#w`6`;+9+hihW2WnKVPQ20LR94h+(p)R$Y!Q zj_3ZEY+e@NH0f6VjLND)sh+Cvfo3CpcXw?`$@a^@CyLrAKIpjL8G z`;cDLqvK=ER)$q)+6vMKlxn!!SzWl>Ib9Ys9L)L0IWr*Ox;Rk#(Dpqf;wapY_EYL8 zKFrV)Q8BBKO4$r2hON%g=r@lPE;kBUVYVG`uxx~QI>9>MCXw_5vnmDsm|^KRny929 zeKx>F(LDs#K4FGU*k3~GX`A!)l8&|tyan-rBHBm6XaB5hc5sGKWwibAD7&3M-gh1n z2?eI7E2u{(^z#W~wU~dHSfy|m)%PY454NBxED)y-T3AO`CLQxklcC1I@Y`v4~SEI#Cm> z-cjqK6I?mypZapi$ZK;y&G+|#D=woItrajg69VRD+Fu8*UxG6KdfFmFLE}HvBJ~Y) zC&c-hr~;H2Idnsz7_F~MKpBZldh)>itc1AL0>4knbVy#%pUB&9vqL1Kg*^aU`k#(p z=A%lur(|$GWSqILaWZ#2xj(&lheSiA|N6DOG?A|$!aYM)?oME6ngnfLw0CA79WA+y zhUeLbMw*VB?drVE_D~3DWVaD>8x?_q>f!6;)i3@W<=kBZBSE=uIU60SW)qct?AdM zXgti8&O=}QNd|u%Fpxr172Kc`sX^@fm>Fxl8fbFalJYci_GGoIzU*~U*I!QLz? z4NYk^=JXBS*Uph@51da-v;%?))cB^(ps}y8yChu7CzyC9SX{jAq13zdnqRHRvc{ha zcPmgCUqAJ^1RChMCCz;ZN*ap{JPoE<1#8nNObDbAt6Jr}Crq#xGkK@w2mLhIUecvy z#?s~?J()H*?w9K`_;S+8TNVkHSk}#yvn+|~jcB|he}OY(zH|7%EK%-Tq=)18730)v zM3f|=oFugXq3Lqn={L!wx|u(ycZf(Te11c3?^8~aF; zNMC)gi?nQ#S$s{46yImv_7@4_qu|XXEza~);h&cr*~dO@#$LtKZa@@r$8PD^jz{D6 zk~5;IJBuQjsKk+8i0wzLJ2=toMw4@rw7(|6`7*e|V(5-#ZzRirtkXBO1oshQ&0>z&HAtSF8+871e|ni4gLs#`3v7gnG#^F zDv!w100_HwtU}B2T!+v_YDR@-9VmoGW+a76oo4yy)o`MY(a^GcIvXW+4)t{lK}I-& zl-C=(w_1Z}tsSFjFd z3iZjkO6xnjLV3!EE?ex9rb1Zxm)O-CnWPat4vw08!GtcQ3lHD+ySRB*3zQu-at$rj zzBn`S?5h=JlLXX8)~Jp%1~YS6>M8c-Mv~E%s7_RcvIYjc-ia`3r>dvjxZ6=?6=#OM zfsv}?hGnMMdi9C`J9+g)5`M9+S79ug=!xE_XcHdWnIRr&hq$!X7aX5kJV8Q(6Lq?|AE8N2H z37j{DPDY^Jw!J>~>Mwaja$g%q1sYfH4bUJFOR`x=pZQ@O(-4b#5=_Vm(0xe!LW>YF zO4w`2C|Cu%^C9q9B>NjFD{+qt)cY3~(09ma%mp3%cjFsj0_93oVHC3)AsbBPuQNBO z`+zffU~AgGrE0K{NVR}@oxB4&XWt&pJ-mq!JLhFWbnXf~H%uU?6N zWJ7oa@``Vi$pMWM#7N9=sX1%Y+1qTGnr_G&h3YfnkHPKG}p>i{fAG+(klE z(g~u_rJXF48l1D?;;>e}Ra{P$>{o`jR_!s{hV1Wk`vURz`W2c$-#r9GM7jgs2>um~ zouGlCm92rOiLITzf`jgl`v2qYw^!Lh0YwFHO1|3Krp8ztE}?#2+>c)yQlNw%5e6w5 zIm9BKZN5Q9b!tX`Zo$0RD~B)VscWp(FR|!a!{|Q$={;ZWl%10vBzfgWn}WBe!%cug z^G%;J-L4<6&aCKx@@(Grsf}dh8fuGT+TmhhA)_16uB!t{HIAK!B-7fJLe9fsF)4G- zf>(~ⅅ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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..93ec5c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jan 16 19:59:54 EET 2020 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME 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/img/leankeykeyboard_logo_small.png b/img/leankeykeyboard_logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..1c557d5aabb7070ace4eca174b8a8dbece575d67 GIT binary patch literal 5687 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 literal 0 HcmV?d00001 diff --git a/img/screen5.png b/img/screen5.png new file mode 100644 index 0000000000000000000000000000000000000000..cef39ca34cdded1f7b6427ce5c352d7fab927692 GIT binary patch literal 39653 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 lu)3Sl?DVj>Nz9{za`q8SX{gRfXrN2aS!elq@@M + variant.outputs.each { output -> + def project = "LeanKeyboard" + // Latest gradle fix: https://stackoverflow.com/questions/62075122/no-such-property-variantconfiguration-for-class + def buildType = variant.buildType.name.take(1) + def version = variant.versionName + def flavor = variant.productFlavors[-1].name + + def newApkName = sprintf("%s_v%s_%s_%s.apk", [project, version, flavor, buildType]) + + output.outputFileName = new File(newApkName) + } + } + + lintOptions { + abortOnError true + disable 'MissingTranslation' + disable 'NewApi' + } + + // gradle 4.6 migration: disable dimensions mechanism + // more: https://proandroiddev.com/advanced-android-flavors-part-4-a-new-version-fc2ad80c01bb + flavorDimensions "default" + + productFlavors { + playstore { + applicationId "org.liskovsoft.androidtv.rukeyboard" + } + origin { + applicationId "com.liskovsoft.leankeyboard" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + androidTestImplementation(project.properties.espressoCoreVersion, { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testImplementation project.properties.junitVersion + implementation project.properties.appCompatXVersion + implementation project.properties.leanbackCompatXVersion + implementation project.properties.constraintXVersion + implementation project.properties.designXVersion + implementation project.properties.voiceOverlayVersion +} 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..4a3fad0 --- /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 androidx.test.InstrumentationRegistry; +import androidx.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..653ab3a --- /dev/null +++ b/leankeykeyboard/src/main/AndroidManifest.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/PermissionsActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/PermissionsActivity.java new file mode 100644 index 0000000..a8275e5 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/PermissionsActivity.java @@ -0,0 +1,43 @@ +package com.liskovsoft.leankeyboard.activity; + +import android.content.Intent; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; +import com.liskovsoft.leankeyboard.helpers.PermissionHelpers; +import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver; + +public class PermissionsActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + checkPermissions(); + } + + @Override + protected void onStop() { + super.onStop(); + + // restart kbd service + Intent intent = new Intent(this, RestartServiceReceiver.class); + sendBroadcast(intent); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + checkPermissions(); + } + + private void checkPermissions() { + if (!PermissionHelpers.hasMicPermissions(this)) { + PermissionHelpers.verifyMicPermissions(this); + } else if (!PermissionHelpers.hasStoragePermissions(this)) { + PermissionHelpers.verifyStoragePermissions(this); + } else { + finish(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbLayoutActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbLayoutActivity.java new file mode 100644 index 0000000..9def7fb --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbLayoutActivity.java @@ -0,0 +1,26 @@ +package com.liskovsoft.leankeyboard.activity.settings; + +import android.content.Intent; +import android.os.Bundle; +import androidx.fragment.app.FragmentActivity; +import androidx.leanback.app.GuidedStepSupportFragment; +import com.liskovsoft.leankeyboard.fragments.settings.KbLayoutFragment; +import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver; + +public class KbLayoutActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + GuidedStepSupportFragment.addAsRoot(this, new KbLayoutFragment(), android.R.id.content); + } + + @Override + protected void onStop() { + super.onStop(); + + // restart kbd service + Intent intent = new Intent(this, RestartServiceReceiver.class); + sendBroadcast(intent); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity.java new file mode 100644 index 0000000..c6cc187 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity.java @@ -0,0 +1,26 @@ +package com.liskovsoft.leankeyboard.activity.settings; + +import android.content.Intent; +import android.os.Bundle; +import androidx.fragment.app.FragmentActivity; +import androidx.leanback.app.GuidedStepSupportFragment; +import com.liskovsoft.leankeyboard.fragments.settings.KbSettingsFragment; +import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver; + +public class KbSettingsActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + GuidedStepSupportFragment.addAsRoot(this, new KbSettingsFragment(), android.R.id.content); + } + + @Override + protected void onStop() { + super.onStop(); + + // restart kbd service + Intent intent = new Intent(this, RestartServiceReceiver.class); + sendBroadcast(intent); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity2.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity2.java new file mode 100644 index 0000000..afbfa2b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/activity/settings/KbSettingsActivity2.java @@ -0,0 +1,4 @@ +package com.liskovsoft.leankeyboard.activity.settings; + +public class KbSettingsActivity2 extends KbSettingsActivity { +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardBuilder.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardBuilder.java new file mode 100644 index 0000000..85e2bc3 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardBuilder.java @@ -0,0 +1,10 @@ +package com.liskovsoft.leankeyboard.addons.keyboards; + +import android.inputmethodservice.Keyboard; +import androidx.annotation.Nullable; + +public interface KeyboardBuilder { + Keyboard createAbcKeyboard(); + Keyboard createSymKeyboard(); + Keyboard createNumKeyboard(); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardFactory.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardFactory.java new file mode 100644 index 0000000..e237be1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardFactory.java @@ -0,0 +1,10 @@ +package com.liskovsoft.leankeyboard.addons.keyboards; + +import android.content.Context; + +import java.util.List; + +public interface KeyboardFactory { + List getAllAvailableKeyboards(Context context); + boolean needUpdate(); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardInfo.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardInfo.java new file mode 100644 index 0000000..800b50a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardInfo.java @@ -0,0 +1,12 @@ +package com.liskovsoft.leankeyboard.addons.keyboards; + +public interface KeyboardInfo { + String getLangCode(); + void setLangCode(String langCode); + String getLangName(); + void setLangName(String langName); + boolean isEnabled(); + void setEnabled(boolean enabled); + boolean isAzerty(); + void setIsAzerty(boolean enabled); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardManager.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardManager.java new file mode 100644 index 0000000..c0947e9 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardManager.java @@ -0,0 +1,103 @@ +package com.liskovsoft.leankeyboard.addons.keyboards; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.ResKeyboardFactory; + +import java.util.ArrayList; +import java.util.List; + +public class KeyboardManager { + private final Context mContext; + private final KeyboardStateManager mStateManager; + private List mKeyboardBuilders; + private List mAllKeyboards; + private final KeyboardFactory mKeyboardFactory; + private int mKeyboardIndex = 0; + + public static class KeyboardData { + public Keyboard abcKeyboard; + public Keyboard symKeyboard; + public Keyboard numKeyboard; + } + + public KeyboardManager(Context ctx) { + mContext = ctx; + mStateManager = new KeyboardStateManager(mContext, this); + mKeyboardFactory = new ResKeyboardFactory(mContext); + mStateManager.restore(); + } + + public void load() { + mKeyboardBuilders = mKeyboardFactory.getAllAvailableKeyboards(mContext); + mAllKeyboards = buildAllKeyboards(); + } + + private List buildAllKeyboards() { + List keyboards = new ArrayList<>(); + if (!mKeyboardBuilders.isEmpty()) { + for (KeyboardBuilder builder : mKeyboardBuilders) { + KeyboardData data = new KeyboardData(); + data.abcKeyboard = builder.createAbcKeyboard(); + data.symKeyboard = builder.createSymKeyboard(); + data.numKeyboard = builder.createNumKeyboard(); + + keyboards.add(data); + } + } + return keyboards; + } + + /** + * Performs callback to event handlers + */ + private void onNextKeyboard() { + mStateManager.onNextKeyboard(); + } + + /** + * Get next keyboard from internal source (looped) + */ + public KeyboardData next() { + if (mKeyboardFactory.needUpdate() || mAllKeyboards == null) { + load(); + } + + ++mKeyboardIndex; + + mKeyboardIndex = mKeyboardIndex < mAllKeyboards.size() ? mKeyboardIndex : 0; + + KeyboardData kbd = mAllKeyboards.get(mKeyboardIndex); + + if (kbd == null) { + throw new IllegalStateException(String.format("Keyboard %s not initialized", mKeyboardIndex)); + } + + onNextKeyboard(); + + return kbd; + } + + public int getIndex() { + return mKeyboardIndex; + } + + public void setIndex(int idx) { + mKeyboardIndex = idx; + } + + /** + * Get current keyboard + */ + public KeyboardData get() { + if (mAllKeyboards == null) { + load(); + } + + if (mAllKeyboards.size() <= mKeyboardIndex) { + mKeyboardIndex = 0; + } + + return mAllKeyboards.get(mKeyboardIndex); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardStateManager.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardStateManager.java new file mode 100644 index 0000000..a6fb483 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/KeyboardStateManager.java @@ -0,0 +1,25 @@ +package com.liskovsoft.leankeyboard.addons.keyboards; + +import android.content.Context; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; + +public class KeyboardStateManager { + private final Context mContext; + private final KeyboardManager mManager; + private final LeanKeyPreferences mPrefs; + + public KeyboardStateManager(Context context, KeyboardManager manager) { + mContext = context; + mManager = manager; + mPrefs = LeanKeyPreferences.instance(mContext); + } + + public void restore() { + int idx = mPrefs.getKeyboardIndex(); + mManager.setIndex(idx); + } + + public void onNextKeyboard() { + mPrefs.setKeyboardIndex(mManager.getIndex()); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOn.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOn.java new file mode 100644 index 0000000..968dccc --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnImpl.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnImpl.java new file mode 100644 index 0000000..334b0ae --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.SparseIntArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.SparseArrayCompat; +import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnsFactory.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnsFactory.java new file mode 100644 index 0000000..90fffd8 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/AddOnsFactory.java @@ -0,0 +1,355 @@ +/* + * 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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.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.liskovsoft.keyboardaddons.apklangfactory.AnySoftKeyboard; +import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.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 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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/Support.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/Support.java new file mode 100644 index 0000000..1484c60 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/addons/Support.java @@ -0,0 +1,64 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons; + +import android.content.Context; +import android.content.res.Resources; +import android.util.SparseIntArray; + +import androidx.annotation.NonNull; +import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.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 implements KeyboardFactory { + 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 ApkLangKeyboardFactory() { + super(TAG, "com.liskovsoft.leankey.langpack.KEYBOARD", "com.liskovsoft.leankey.langpack.keyboards", + "Keyboards", "Keyboard", + 0, true); + } + + @Override + public List getAllAvailableKeyboards(Context context) { + return getAllAddOns(context); + } + + @Override + public boolean needUpdate() { + // TODO: implement need update + return false; + } + + 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 ApkKeyboardAddOnAndBuilder 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 ApkKeyboardAddOnAndBuilder addOn = allAddOns.get(0); + editor.putBoolean(addOn.getId(), true); + editor.commit(); + enabledAddOns.add(addOn); + } + + for (final ApkKeyboardAddOnAndBuilder addOn : enabledAddOns) { + Logger.d(TAG, "Factory provided addon: %s", addOn.getId()); + } + + return enabledAddOns; + } + + @Override + protected ApkKeyboardAddOnAndBuilder 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 ApkKeyboardAddOnAndBuilder(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).createAbcKeyboard(); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/BuildConfig.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/BuildConfig.java new file mode 100644 index 0000000..a52b1e9 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/BuildConfig.java @@ -0,0 +1,6 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log; + +public class BuildConfig { + public final static boolean TESTING_BUILD = true; + public final static boolean DEBUG = true; +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogCatLogProvider.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogCatLogProvider.java new file mode 100644 index 0000000..34d9eec --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogCatLogProvider.java @@ -0,0 +1,48 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log; + +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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogProvider.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/LogProvider.java new file mode 100644 index 0000000..b0c66a6 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log; + +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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/Logger.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/Logger.java new file mode 100644 index 0000000..e8f756b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/Logger.java @@ -0,0 +1,228 @@ +/* + * 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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log; + + +import androidx.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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/NullLogProvider.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/NullLogProvider.java new file mode 100644 index 0000000..f041d8a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/log/NullLogProvider.java @@ -0,0 +1,39 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log; + +/** + * 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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlUtils.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlUtils.java new file mode 100644 index 0000000..5f8aeb6 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.xml; + +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/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlWriter.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlWriter.java new file mode 100644 index 0000000..a0a0c15 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/extkeyboards/utils/xml/XmlWriter.java @@ -0,0 +1,252 @@ + +/* + * 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.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.xml; + +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. + */ + 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 max 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/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/CheckedSource.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/CheckedSource.java new file mode 100644 index 0000000..62a32ca --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/CheckedSource.java @@ -0,0 +1,14 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards; + +import java.util.List; + +public interface CheckedSource { + List getItems(); + + interface CheckedItem { + long getId(); + String getTitle(); + void onClick(boolean checked); + boolean getChecked(); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/KeyboardInfoAdapter.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/KeyboardInfoAdapter.java new file mode 100644 index 0000000..efc9f8c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/KeyboardInfoAdapter.java @@ -0,0 +1,62 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards; + +import android.content.Context; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo; + +import java.util.ArrayList; +import java.util.List; + +public class KeyboardInfoAdapter implements CheckedSource { + private final Context mContext; + private final List mInfos; + + public KeyboardInfoAdapter(Context context) { + mContext = context; + mInfos = ResKeyboardInfo.getAllKeyboardInfos(context); + } + + @Override + public List getItems() { + List result = new ArrayList<>(); + + int counter = 99; + + for (KeyboardInfo info : mInfos) { + int finalCounter = counter++; + + CheckedItem item = new CheckedItem() { + private final KeyboardInfo mInfo = info; + private final int mCounter = finalCounter; + + @Override + public long getId() { + return mCounter; + } + + @Override + public String getTitle() { + return mInfo.getLangName(); + } + + @Override + public void onClick(boolean checked) { + if (mInfo.isEnabled() == checked) { + return; + } + + mInfo.setEnabled(checked); + ResKeyboardInfo.updateAllKeyboardInfos(mContext, mInfos); + } + + @Override + public boolean getChecked() { + return mInfo.isEnabled(); + } + }; + + result.add(item); + } + + return result; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardFactory.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardFactory.java new file mode 100644 index 0000000..b7b6a60 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardFactory.java @@ -0,0 +1,139 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.text.Layout; +import android.util.Log; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardBuilder; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardFactory; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeyboard.utils.TextDrawable; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ResKeyboardFactory implements KeyboardFactory { + private static final String TAG = ResKeyboardFactory.class.getSimpleName(); + private final Context mContext; + private Map mCachedSpace; + + public ResKeyboardFactory(Context ctx) { + mContext = ctx; + mCachedSpace = new HashMap<>(); + } + + @Override + public List getAllAvailableKeyboards(Context context) { + List result = new ArrayList<>(); + List infos = ResKeyboardInfo.getAllKeyboardInfos(context); + + for (final KeyboardInfo info : infos) { + if (info.isEnabled()) { + result.add(createKeyboard(info)); + } + } + + // at least one kbd should be enabled + if (result.isEmpty()) { + KeyboardInfo defaultKbd = findDefaultKeyboard(infos); + result.add(createKeyboard(defaultKbd)); + defaultKbd.setEnabled(true); + //ResKeyboardInfo.updateAllKeyboardInfos(mContext, infos); + } + + return result; + } + + private KeyboardInfo findDefaultKeyboard(List infos) { + KeyboardInfo defaultKeyboard = infos.get(0); + + if (LeanKeyPreferences.instance(mContext).getAutodetectLayout()) { + Locale defaultLocale = Locale.getDefault(); + String lang = defaultLocale.getLanguage(); + + for (final KeyboardInfo info : infos) { + if (info.getLangCode().startsWith(lang)) { + defaultKeyboard = info; + break; + } + } + } + + return defaultKeyboard; + } + + /** + * NOTE: create keyboard from xml data + */ + private KeyboardBuilder createKeyboard(final KeyboardInfo info) { + return new KeyboardBuilder() { + private final String langCode = info.getLangCode(); + + @Override + public Keyboard createAbcKeyboard() { + String prefix = info.isAzerty() ? "azerty_" : "qwerty_"; + int kbResId = mContext.getResources().getIdentifier(prefix + langCode, "xml", mContext.getPackageName()); + Keyboard keyboard = new Keyboard(mContext, kbResId); + Log.d(TAG, "Creating keyboard... " + info.getLangName()); + return localizeKeys(keyboard, info); + } + + @Override + public Keyboard createSymKeyboard() { + Keyboard keyboard = new Keyboard(mContext, R.xml.sym_en_us); + return localizeKeys(keyboard, info); + } + + @Override + public Keyboard createNumKeyboard() { + return new Keyboard(mContext, R.xml.number); + } + }; + } + + @Override + public boolean needUpdate() { + return ResKeyboardInfo.needUpdate(); + } + + private Keyboard localizeKeys(Keyboard keyboard, KeyboardInfo info) { + List keys = keyboard.getKeys(); + + for (Key key : keys) { + if (key.codes[0] == LeanbackKeyboardView.ASCII_SPACE) { + localizeSpace(key, info); + break; + } + } + + return keyboard; + } + + private void localizeSpace(Key key, KeyboardInfo info) { + if (mCachedSpace.containsKey(info.getLangCode())) { + key.icon = mCachedSpace.get(info.getLangCode()); + return; + } + + TextDrawable drawable = new TextDrawable(mContext, key.icon); + drawable.setText(info.getLangName()); + drawable.setTextAlign(Layout.Alignment.ALIGN_CENTER); + //Customize text size and color + drawable.setTextColor(Color.WHITE); + drawable.setTextSizeFactor(0.3f); + drawable.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); + key.icon = drawable; + + mCachedSpace.put(info.getLangCode(), drawable); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardInfo.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardInfo.java new file mode 100644 index 0000000..a47b224 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/keyboards/intkeyboards/ResKeyboardInfo.java @@ -0,0 +1,111 @@ +package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import androidx.annotation.NonNull; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; +import java.util.List; + +public class ResKeyboardInfo implements KeyboardInfo { + private static boolean sNeedUpdate; + private boolean mEnabled; + private String mLangCode; + private String mLangName; + private boolean mIsAzerty; + + public static List getAllKeyboardInfos(Context ctx) { + List result = new ArrayList<>(); + String[] langs = ctx.getResources().getStringArray(R.array.additional_languages); + for (final String langPair : langs) { + String[] pairs = langPair.split("\\|"); + final String langName = pairs[0]; + final String langCode = pairs[1]; + final boolean isAzerty = pairs.length >= 3 && "azerty".equals(pairs[2]); + KeyboardInfo info = new ResKeyboardInfo(); + info.setLangName(langName); + info.setLangCode(langCode); + info.setIsAzerty(isAzerty); + // sync with prefs + syncWithPrefs(ctx, info); + result.add(info); + } + sNeedUpdate = false; + return result; + } + + public static void updateAllKeyboardInfos(Context ctx, List infos) { + for (KeyboardInfo info : infos) { + // update prefs + updatePrefs(ctx, info); + } + sNeedUpdate = true; + } + + private static void syncWithPrefs(Context ctx, KeyboardInfo info) { + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx); + final boolean kbdEnabled = sharedPreferences.getBoolean(info.toString(), false); + info.setEnabled(kbdEnabled); + } + + private static void updatePrefs(Context ctx, KeyboardInfo info) { + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx); + + final SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(info.toString(), info.isEnabled()); + editor.apply(); + } + + public static boolean needUpdate() { + return sNeedUpdate; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public String getLangCode() { + return mLangCode; + } + + @Override + public String getLangName() { + return mLangName; + } + + @Override + public void setLangName(String langName) { + mLangName = langName; + } + + @Override + public void setLangCode(String langCode) { + mLangCode = langCode; + } + + @Override + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public boolean isAzerty() { + return mIsAzerty; + } + + @Override + public void setIsAzerty(boolean isAzerty) { + mIsAzerty = isAzerty; + } + + @NonNull + @Override + public String toString() { + return String.format("{Name: %s, Code: %s, IsAzerty: %b}", mLangName, mLangCode, mIsAzerty); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/KeyboardWrapper.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/KeyboardWrapper.java new file mode 100644 index 0000000..bd861c1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/KeyboardWrapper.java @@ -0,0 +1,99 @@ +package com.liskovsoft.leankeyboard.addons.resize; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import androidx.annotation.Nullable; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.List; + +public class KeyboardWrapper extends Keyboard { + private Keyboard mKeyboard; + private int mHeight = -1; + private float mHeightFactor = 1.0f; + private float mWidthFactor = 1.0f; + + public KeyboardWrapper(Context context, int xmlLayoutResId) { + super(context, xmlLayoutResId); + } + + public KeyboardWrapper(Context context, int xmlLayoutResId, int modeId, int width, int height) { + super(context, xmlLayoutResId, modeId, width, height); + } + + public KeyboardWrapper(Context context, int xmlLayoutResId, int modeId) { + super(context, xmlLayoutResId, modeId); + } + + public KeyboardWrapper(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) { + super(context, layoutTemplateResId, characters, columns, horizontalPadding); + } + + public static KeyboardWrapper from(Keyboard keyboard, Context context) { + KeyboardWrapper wrapper = new KeyboardWrapper(context, R.xml.empty_kbd); + wrapper.mKeyboard = keyboard; + + return wrapper; + } + + @Override + public List getKeys() { + return mKeyboard.getKeys(); + } + + @Override + public List getModifierKeys() { + return mKeyboard.getModifierKeys(); + } + + @Override + public int getHeight() { + return (int)(mKeyboard.getHeight() * mHeightFactor); + } + + @Override + public int getMinWidth() { + return (int)(mKeyboard.getMinWidth() * mWidthFactor); + } + + @Override + public boolean setShifted(boolean shiftState) { + return mKeyboard.setShifted(shiftState); + } + + @Override + public boolean isShifted() { + return mKeyboard.isShifted(); + } + + @Override + public int getShiftKeyIndex() { + return mKeyboard.getShiftKeyIndex(); + } + + @Override + public int[] getNearestKeys(int x, int y) { + return mKeyboard.getNearestKeys(x, y); + } + + public void setHeightFactor(float factor) { + mHeightFactor = factor; + } + + public void setWidthFactor(float factor) { + mWidthFactor = factor; + } + + /** + * Wrapper fix: {@link LeanbackKeyboardContainer#onModeChangeClick} + */ + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof Keyboard) { + return mKeyboard.equals(obj); + } + + return false; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/ResizeableLeanbackKeyboardView.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/ResizeableLeanbackKeyboardView.java new file mode 100644 index 0000000..3555426 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/resize/ResizeableLeanbackKeyboardView.java @@ -0,0 +1,76 @@ +package com.liskovsoft.leankeyboard.addons.resize; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; + +import java.util.List; + +public class ResizeableLeanbackKeyboardView extends LeanbackKeyboardView { + private final LeanKeyPreferences mPrefs; + private final int mKeyTextSizeOrigin; + private final int mModeChangeTextSizeOrigin; + private final float mSizeFactor = 1.3f; + private int mKeyOriginWidth; + + public ResizeableLeanbackKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + mPrefs = LeanKeyPreferences.instance(getContext()); + mKeyTextSizeOrigin = mKeyTextSize; + mModeChangeTextSizeOrigin = mModeChangeTextSize; + } + + @Override + public void setKeyboard(Keyboard keyboard) { + if (mPrefs.getEnlargeKeyboard()) { + mKeyTextSize = (int) (mKeyTextSizeOrigin * mSizeFactor); + mModeChangeTextSize = (int) (mModeChangeTextSizeOrigin * mSizeFactor); + + keyboard = updateKeyboard(keyboard); + } else { + mKeyTextSize = mKeyTextSizeOrigin; + mModeChangeTextSize = mModeChangeTextSizeOrigin; + } + + mPaint.setTextSize(mKeyTextSize); + + super.setKeyboard(keyboard); + } + + private Keyboard updateKeyboard(Keyboard keyboard) { + List keys = keyboard.getKeys(); + + if (isNotSizedYet(keys.get(0))) { + for (Key key : keys) { + key.width *= mSizeFactor; + key.height *= mSizeFactor; + key.gap *= mSizeFactor; + key.x *= mSizeFactor; + key.y *= mSizeFactor; + } + } + + KeyboardWrapper wrapper = KeyboardWrapper.from(keyboard, getContext()); + wrapper.setHeightFactor(mSizeFactor); + wrapper.setWidthFactor(mSizeFactor); + + return wrapper; + } + + private boolean isNotSizedYet(Key key) { + boolean result = false; + + if (mKeyOriginWidth == 0) { + mKeyOriginWidth = key.width; + } + + if (mKeyOriginWidth == key.width) { + result = true; + } + + return result; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/theme/ThemeManager.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/theme/ThemeManager.java new file mode 100644 index 0000000..234170c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/theme/ThemeManager.java @@ -0,0 +1,159 @@ +package com.liskovsoft.leankeyboard.addons.theme; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import androidx.core.content.ContextCompat; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeykeyboard.R; + +public class ThemeManager { + private static final String TAG = ThemeManager.class.getSimpleName(); + private final Context mContext; + private final RelativeLayout mRootView; + private final LeanKeyPreferences mPrefs; + + public ThemeManager(Context context, RelativeLayout rootView) { + mContext = context; + mRootView = rootView; + mPrefs = LeanKeyPreferences.instance(mContext); + } + + public void updateKeyboardTheme() { + String currentThemeId = mPrefs.getCurrentTheme(); + + if (LeanKeyPreferences.THEME_DEFAULT.equals(currentThemeId)) { + applyKeyboardColors( + R.color.keyboard_background, + R.color.candidate_background, + R.color.enter_key_font_color, + R.color.key_text_default + ); + applyShiftDrawable(-1); + } else { + applyForTheme((String themeId) -> { + Resources resources = mContext.getResources(); + int keyboardBackgroundResId = resources.getIdentifier("keyboard_background_" + themeId.toLowerCase(), "color", mContext.getPackageName()); + int candidateBackgroundResId = resources.getIdentifier("candidate_background_" + themeId.toLowerCase(), "color", mContext.getPackageName()); + int enterFontColorResId = resources.getIdentifier("enter_key_font_color_" + themeId.toLowerCase(), "color", mContext.getPackageName()); + int keyTextColorResId = resources.getIdentifier("key_text_default_" + themeId.toLowerCase(), "color", mContext.getPackageName()); + + applyKeyboardColors( + keyboardBackgroundResId, + candidateBackgroundResId, + enterFontColorResId, + keyTextColorResId + ); + + int shiftLockOnResId = resources.getIdentifier("ic_ime_shift_lock_on_" + themeId.toLowerCase(), "drawable", mContext.getPackageName()); + + applyShiftDrawable(shiftLockOnResId); + }); + } + } + + public void updateSuggestionsTheme() { + String currentTheme = mPrefs.getCurrentTheme(); + + if (LeanKeyPreferences.THEME_DEFAULT.equals(currentTheme)) { + applySuggestionsColors( + R.color.candidate_font_color + ); + } else { + applyForTheme((String themeId) -> { + Resources resources = mContext.getResources(); + int candidateFontColorResId = resources.getIdentifier("candidate_font_color_" + themeId.toLowerCase(), "color", mContext.getPackageName()); + applySuggestionsColors(candidateFontColorResId); + }); + } + } + + private void applyKeyboardColors( + int keyboardBackground, + int candidateBackground, + int enterFontColor, + int keyTextColor) { + + RelativeLayout rootLayout = mRootView.findViewById(R.id.root_ime); + + if (rootLayout != null) { + rootLayout.setBackgroundColor(ContextCompat.getColor(mContext, keyboardBackground)); + } + + View candidateLayout = mRootView.findViewById(R.id.candidate_background); + + if (candidateLayout != null) { + candidateLayout.setBackgroundColor(ContextCompat.getColor(mContext, candidateBackground)); + } + + Button enterButton = mRootView.findViewById(R.id.enter); + + if (enterButton != null) { + enterButton.setTextColor(ContextCompat.getColor(mContext, enterFontColor)); + } + + LeanbackKeyboardView keyboardView = mRootView.findViewById(R.id.main_keyboard); + + if (keyboardView != null) { + keyboardView.setKeyTextColor(ContextCompat.getColor(mContext, keyTextColor)); + } + } + + private void applySuggestionsColors(int candidateFontColor) { + LinearLayout suggestions = mRootView.findViewById(R.id.suggestions); + + if (suggestions != null) { + int childCount = suggestions.getChildCount(); + + Log.d(TAG, "Number of suggestions: " + childCount); + + for (int i = 0; i < childCount; i++) { + View child = suggestions.getChildAt(i); + + Button candidateButton = child.findViewById(R.id.text); + + if (candidateButton != null) { + candidateButton.setTextColor(ContextCompat.getColor(mContext, candidateFontColor)); + } + } + } + } + + private void applyShiftDrawable(int resId) { + LeanbackKeyboardView keyboardView = mRootView.findViewById(R.id.main_keyboard); + + if (keyboardView != null && resId > 0) { + Drawable drawable = ContextCompat.getDrawable(mContext, resId); + + keyboardView.setCapsLockDrawable(drawable); + } + } + + private void applyForTheme(ThemeCallback callback) { + String currentThemeId = mPrefs.getCurrentTheme(); + Resources resources = mContext.getResources(); + String[] themes = resources.getStringArray(R.array.keyboard_themes); + + for (String theme : themes) { + String[] split = theme.split("\\|"); + String themeName = split[0]; + String themeId = split[1]; + + if (currentThemeId.equals(themeId)) { + callback.onThemeFound(themeId); + + break; + } + } + } + + private interface ThemeCallback { + void onThemeFound(String themeId); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/ActivityListener.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/ActivityListener.java new file mode 100644 index 0000000..fb16cf1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/ActivityListener.java @@ -0,0 +1,7 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import android.content.Intent; + +interface ActivityListener { + void onActivityResult(int requestCode, int resultCode, Intent data); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerCallback.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerCallback.java new file mode 100644 index 0000000..95721e7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerCallback.java @@ -0,0 +1,5 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +public interface RecognizerCallback { + void openSearchPage(String searchText); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentActivity.java new file mode 100644 index 0000000..b9ba412 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentActivity.java @@ -0,0 +1,29 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import android.content.Intent; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +public class RecognizerIntentActivity extends AppCompatActivity { + public static RecognizerCallback sCallback; + private VoiceSearchBridge mBridge; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mBridge = new VoiceSearchBridge(this, searchText -> sCallback.openSearchPage(searchText)); + + mBridge.displaySpeechRecognizers(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + mBridge.onActivityResult(requestCode, resultCode, data); + + finish(); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentWrapper.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentWrapper.java new file mode 100644 index 0000000..eb3f896 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/RecognizerIntentWrapper.java @@ -0,0 +1,26 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import android.content.Context; +import android.content.Intent; + +public class RecognizerIntentWrapper { + private final Context mContext; + private RecognizerCallback mCallback; + + public RecognizerIntentWrapper(Context context) { + mContext = context; + } + + public void setListener(RecognizerCallback callback) { + mCallback = callback; + } + + public void startListening() { + if (mCallback != null) { + RecognizerIntentActivity.sCallback = mCallback; + Intent intent = new Intent(mContext, RecognizerIntentActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SearchCallback.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SearchCallback.java new file mode 100644 index 0000000..e35e430 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SearchCallback.java @@ -0,0 +1,5 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +interface SearchCallback { + void openSearchPage(String searchText); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SystemVoiceDialog.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SystemVoiceDialog.java new file mode 100644 index 0000000..e7defc4 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/SystemVoiceDialog.java @@ -0,0 +1,45 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.speech.RecognizerIntent; + +import java.util.List; + +class SystemVoiceDialog implements VoiceDialog, ActivityListener { + private static final int SPEECH_REQUEST_CODE = 11; + private final Activity mActivity; + private final SearchCallback mCallback; + + public SystemVoiceDialog(Activity activity, SearchCallback callback) { + mActivity = activity; + mCallback = callback; + } + + public boolean displaySpeechRecognizer() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + try { + mActivity.startActivityForResult(intent, SPEECH_REQUEST_CODE); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + return false; + } + + return true; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // got speech-to-text result, switch to the search page + if (requestCode == SPEECH_REQUEST_CODE && resultCode == -1) { + List results = data.getStringArrayListExtra( + RecognizerIntent.EXTRA_RESULTS); + if (results != null && results.size() > 0) { + mCallback.openSearchPage(results.get(0)); + } + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceDialog.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceDialog.java new file mode 100644 index 0000000..2e0bee7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceDialog.java @@ -0,0 +1,5 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +interface VoiceDialog { + boolean displaySpeechRecognizer(); +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceOverlayDialog.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceOverlayDialog.java new file mode 100644 index 0000000..2a3eaf1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceOverlayDialog.java @@ -0,0 +1,67 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import androidx.appcompat.app.AppCompatActivity; +import com.algolia.instantsearch.voice.VoiceSpeechRecognizer; +import com.algolia.instantsearch.voice.ui.Voice; +import com.algolia.instantsearch.voice.ui.VoiceInputDialogFragment; +import com.algolia.instantsearch.voice.ui.VoicePermissionDialogFragment; + +class VoiceOverlayDialog implements VoiceDialog, VoiceSpeechRecognizer.ResultsListener { + private final AppCompatActivity mActivity; + private final SearchCallback mCallback; + + private enum Tag { + Permission, + Voice + } + + public VoiceOverlayDialog(AppCompatActivity activity, SearchCallback callback) { + mActivity = activity; + mCallback = callback; + } + + @Override + public boolean displaySpeechRecognizer() { + if (!Voice.isRecordAudioPermissionGranted(mActivity)) { + new VoicePermissionDialogFragment() + .show(mActivity.getSupportFragmentManager(), Tag.Permission.name()); + } else { + showVoiceDialog(); + } + + return true; + } + + private void showVoiceDialog() { + VoicePermissionDialogFragment dialog = getPermissionDialog(); + + if (dialog != null) { + dialog.dismiss(); + } + + VoiceInputDialogFragment voiceDialog = getVoiceDialog(); + + if (voiceDialog == null) { + voiceDialog = new VoiceInputDialogFragment(); + } else { + voiceDialog.dismiss(); + } + + voiceDialog.show(mActivity.getSupportFragmentManager(), Tag.Voice.name()); + } + + private VoicePermissionDialogFragment getPermissionDialog() { + return (VoicePermissionDialogFragment) mActivity.getSupportFragmentManager().findFragmentByTag(Tag.Permission.name()); + } + + private VoiceInputDialogFragment getVoiceDialog() { + return (VoiceInputDialogFragment) mActivity.getSupportFragmentManager().findFragmentByTag(Tag.Voice.name()); + } + + @Override + public void onResults(String[] strings) { + if (strings.length > 0) { + mCallback.openSearchPage(strings[0]); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceSearchBridge.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceSearchBridge.java new file mode 100644 index 0000000..dc2ecf3 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/addons/voice/VoiceSearchBridge.java @@ -0,0 +1,43 @@ +package com.liskovsoft.leankeyboard.addons.voice; + +import android.content.Intent; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; + +class VoiceSearchBridge implements SearchCallback { + private final ArrayList mDialogs; + private final AppCompatActivity mActivity; + private final RecognizerCallback mCallback; + + public VoiceSearchBridge(AppCompatActivity activity, RecognizerCallback callback) { + mActivity = activity; + mCallback = callback; + mDialogs = new ArrayList<>(); + mDialogs.add(new SystemVoiceDialog(activity, this)); + mDialogs.add(new VoiceOverlayDialog(activity, this)); + } + + public void displaySpeechRecognizers() { + for (VoiceDialog dialog : mDialogs) { + if (dialog.displaySpeechRecognizer()) { // fist successful attempt is used + break; + } + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + for (VoiceDialog dialog : mDialogs) { + if (dialog instanceof ActivityListener) { + ((ActivityListener) dialog).onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + public void openSearchPage(String searchText) { + if (mCallback != null) { + mCallback.openSearchPage(searchText); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/BaseSettingsFragment.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/BaseSettingsFragment.java new file mode 100644 index 0000000..4f3b8a3 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/BaseSettingsFragment.java @@ -0,0 +1,190 @@ +package com.liskovsoft.leankeyboard.fragments.settings; + +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.leanback.app.GuidedStepSupportFragment; +import androidx.leanback.widget.GuidedAction; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class BaseSettingsFragment extends GuidedStepSupportFragment { + private Map mCheckedActions = new LinkedHashMap<>(); + private Map mNextActions = new LinkedHashMap<>(); + private long mId; + + protected interface OnChecked { + void onChecked(boolean checked); + } + + protected interface GetChecked { + boolean getChecked(); + } + + protected interface OnClick { + void onClick(); + } + + // Radio action + + protected void addRadioAction(int titleResId, int descResId, GetChecked getChecked, OnChecked onChecked) { + addRadioAction(getString(titleResId), getString(descResId), getChecked, onChecked); + } + + protected void addRadioAction(int titleRedId, GetChecked getChecked, OnChecked onChecked) { + addRadioAction(getString(titleRedId), getChecked, onChecked); + } + + protected void addRadioAction(String title, GetChecked getChecked, OnChecked onChecked) { + mCheckedActions.put(mId++, new RadioAction(title, getChecked, onChecked)); + } + + protected void addRadioAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) { + mCheckedActions.put(mId++, new RadioAction(title, desc, getChecked, onChecked)); + } + + // Checked action + + protected void addCheckedAction(int titleResId, int descResId, GetChecked getChecked, OnChecked onChecked) { + addCheckedAction(getString(titleResId), getString(descResId), getChecked, onChecked); + } + + protected void addCheckedAction(int titleRedId, GetChecked getChecked, OnChecked onChecked) { + addCheckedAction(getString(titleRedId), getChecked, onChecked); + } + + protected void addCheckedAction(String title, GetChecked getChecked, OnChecked onChecked) { + mCheckedActions.put(mId++, new CheckedAction(title, getChecked, onChecked)); + } + + protected void addCheckedAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) { + mCheckedActions.put(mId++, new CheckedAction(title, desc, getChecked, onChecked)); + } + + // Nested action + + protected void addNextAction(int resId, OnClick onClick) { + mNextActions.put(mId++, new NextAction(resId, onClick)); + } + + @Override + public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { + for (long id : mCheckedActions.keySet()) { + addCheckedItem(id, mCheckedActions.get(id), actions); + } + + for (long id : mNextActions.keySet()) { + addNextItem(id, mNextActions.get(id), actions); + } + } + + @Override + public void onGuidedActionClicked(GuidedAction action) { + CheckedAction checkedAction = mCheckedActions.get(action.getId()); + + if (checkedAction != null) { + checkedAction.onChecked(action.isChecked()); + } + + NextAction nextAction = mNextActions.get(action.getId()); + + if (nextAction != null) { + nextAction.onClick(); + } + } + + private void addNextItem(long id, NextAction nextAction, List actions) { + GuidedAction action = new GuidedAction.Builder(getActivity()) + .id(id) + .hasNext(true) + .title(nextAction.getResId()).build(); + actions.add(action); + } + + private void addCheckedItem(long id, CheckedAction checkedAction, List actions) { + GuidedAction action = new GuidedAction.Builder(getActivity()) + .checked(checkedAction.isChecked()) + .checkSetId(checkedAction.getItemTypeId()) + .id(id) + .title(checkedAction.getTitle()) + .build(); + + if (checkedAction.getDesc() != null) { + action.setDescription(checkedAction.getDesc()); + } + + actions.add(action); + } + + private static class RadioAction extends CheckedAction { + public RadioAction(String title, GetChecked getChecked, OnChecked onChecked) { + super(title, getChecked, onChecked); + } + + public RadioAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) { + super(title, desc, getChecked, onChecked); + } + + @Override + public int getItemTypeId() { + return GuidedAction.DEFAULT_CHECK_SET_ID; + } + } + + private static class CheckedAction { + private final String mDesc; + private final GetChecked mGetChecked; + private final OnChecked mOnChecked; + private final String mTitle; + + public CheckedAction(String title, GetChecked getChecked, OnChecked onChecked) { + this(title, null, getChecked, onChecked); + } + + public CheckedAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) { + mTitle = title; + mDesc = desc; + mGetChecked = getChecked; + mOnChecked = onChecked; + } + + public String getTitle() { + return mTitle; + } + + public String getDesc() { + return mDesc; + } + + public boolean isChecked() { + return mGetChecked.getChecked(); + } + + public void onChecked(boolean checked) { + mOnChecked.onChecked(checked); + } + + public int getItemTypeId() { + return GuidedAction.CHECKBOX_CHECK_SET_ID; + } + } + + private static class NextAction { + private final int mResId; + private final OnClick mOnClick; + + public NextAction(int resId, OnClick onClick) { + mResId = resId; + mOnClick = onClick; + } + + public int getResId() { + return mResId; + } + + public void onClick() { + mOnClick.onClick(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbLayoutFragment.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbLayoutFragment.java new file mode 100644 index 0000000..dcaa3ff --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbLayoutFragment.java @@ -0,0 +1,43 @@ +package com.liskovsoft.leankeyboard.fragments.settings; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.leanback.widget.GuidanceStylist.Guidance; +import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.KeyboardInfoAdapter; +import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.CheckedSource; +import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.CheckedSource.CheckedItem; +import com.liskovsoft.leankeykeyboard.R; + +public class KbLayoutFragment extends BaseSettingsFragment { + @Override + public void onAttach(Context context) { + super.onAttach(context); + + initCheckedItems(); + } + + @NonNull + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getActivity().getResources().getString(R.string.kb_layout); + String desc = getActivity().getResources().getString(R.string.kb_layout_desc); + Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher); + + return new Guidance( + title, + desc, + "", + icon + ); + } + + private void initCheckedItems() { + CheckedSource source = new KeyboardInfoAdapter(getActivity()); + for (CheckedItem item : source.getItems()) { + addCheckedAction(item.getTitle(), item::getChecked, item::onClick); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbSettingsFragment.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbSettingsFragment.java new file mode 100644 index 0000000..6362fa4 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbSettingsFragment.java @@ -0,0 +1,53 @@ +package com.liskovsoft.leankeyboard.fragments.settings; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.leanback.app.GuidedStepSupportFragment; +import androidx.leanback.widget.GuidanceStylist.Guidance; +import com.liskovsoft.leankeyboard.activity.settings.KbActivationActivity; +import com.liskovsoft.leankeykeyboard.R; + +public class KbSettingsFragment extends BaseSettingsFragment { + @Override + public void onAttach(Context context) { + super.onAttach(context); + + addNextAction(R.string.activate_keyboard, () -> { + Intent intent = new Intent(getActivity(), KbActivationActivity.class); + startActivity(intent); + }); + + addNextAction(R.string.change_layout, () -> startGuidedFragment(new KbLayoutFragment())); + + addNextAction(R.string.change_theme, () -> startGuidedFragment(new KbThemeFragment())); + + addNextAction(R.string.misc, () -> startGuidedFragment(new MiscFragment())); + + addNextAction(R.string.about_desc, () -> startGuidedFragment(new AboutFragment())); + } + + @NonNull + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getActivity().getResources().getString(R.string.ime_name); + String desc = getActivity().getResources().getString(R.string.kb_settings_desc); + Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher); + + return new Guidance( + title, + desc, + "", + icon + ); + } + + private void startGuidedFragment(GuidedStepSupportFragment fragment) { + if (getFragmentManager() != null) { + GuidedStepSupportFragment.add(getFragmentManager(), fragment); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbThemeFragment.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbThemeFragment.java new file mode 100644 index 0000000..7e13ab2 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/KbThemeFragment.java @@ -0,0 +1,51 @@ +package com.liskovsoft.leankeyboard.fragments.settings; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.leanback.widget.GuidanceStylist.Guidance; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeykeyboard.R; + +public class KbThemeFragment extends BaseSettingsFragment { + private Context mContext; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = context; + + initRadioItems(); + } + + @NonNull + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getActivity().getResources().getString(R.string.kb_theme); + String desc = getActivity().getResources().getString(R.string.kb_theme_desc); + Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher); + + return new Guidance( + title, + desc, + "", + icon + ); + } + + private void initRadioItems() { + String[] themes = mContext.getResources().getStringArray(R.array.keyboard_themes); + + LeanKeyPreferences prefs = LeanKeyPreferences.instance(mContext); + String currentTheme = prefs.getCurrentTheme(); + + for (String theme : themes) { + String[] split = theme.split("\\|"); + String themeName = split[0]; + String themeId = split[1]; + addRadioAction(themeName, () -> currentTheme.equals(themeId), (checked) -> prefs.setCurrentTheme(themeId)); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/MiscFragment.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/MiscFragment.java new file mode 100644 index 0000000..979af4c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/fragments/settings/MiscFragment.java @@ -0,0 +1,53 @@ +package com.liskovsoft.leankeyboard.fragments.settings; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.leanback.widget.GuidanceStylist.Guidance; +import com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity2; +import com.liskovsoft.leankeyboard.helpers.Helpers; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeykeyboard.R; + +public class MiscFragment extends BaseSettingsFragment { + private LeanKeyPreferences mPrefs; + private Context mContext; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + mContext = context; + mPrefs = LeanKeyPreferences.instance(getActivity()); + addCheckedAction(R.string.keep_on_screen, R.string.keep_on_screen_desc, mPrefs::getForceShowKeyboard, mPrefs::setForceShowKeyboard); + addCheckedAction(R.string.increase_kbd_size, R.string.increase_kbd_size_desc, mPrefs::getEnlargeKeyboard, mPrefs::setEnlargeKeyboard); + addCheckedAction(R.string.enable_suggestions, R.string.enable_suggestions_desc, mPrefs::getSuggestionsEnabled, mPrefs::setSuggestionsEnabled); + addCheckedAction(R.string.show_launcher_icon, R.string.show_launcher_icon_desc, this::getLauncherIconShown, this::setLauncherIconShown); + addCheckedAction(R.string.enable_cyclic_navigation, R.string.enable_cyclic_navigation_desc, mPrefs::isCyclicNavigationEnabled, mPrefs::setCyclicNavigationEnabled); + } + + @NonNull + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getActivity().getResources().getString(R.string.misc); + String desc = getActivity().getResources().getString(R.string.misc_desc); + Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher); + + return new Guidance( + title, + desc, + "", + icon + ); + } + + private void setLauncherIconShown(boolean shown) { + Helpers.setLauncherIconShown(mContext, KbSettingsActivity2.class, shown); + } + + private boolean getLauncherIconShown() { + return Helpers.getLauncherIconShown(mContext, KbSettingsActivity2.class); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/AppInfoHelpers.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/AppInfoHelpers.java new file mode 100644 index 0000000..50bc724 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/AppInfoHelpers.java @@ -0,0 +1,156 @@ +package com.liskovsoft.leankeyboard.helpers; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ProviderInfo; +import android.content.res.Resources.NotFoundException; +import android.util.Log; +import androidx.core.content.pm.PackageInfoCompat; + +public class AppInfoHelpers { + private static final String TAG = AppInfoHelpers.class.getSimpleName(); + + public static String getAppVersion(Context context) { + return formatAppVersion(getAppVersionName(context), getActivityLabel(context)); + } + + public static String getAppVersionRobust(Context context, String launchActivityName) { + return formatAppVersion(getAppVersionName(context), getActivityLabelRobust(context, launchActivityName)); + } + + private static String formatAppVersion(String version, String label) { + return String.format("%s (%s)", version, label); + } + + public static String getActivityLabelRobust(Context context, String launchActivityName) { + return getActivityLabel(context, launchActivityName); + } + + public static int getAppVersionCode(Context context) { + PackageInfo packageInfo = createPackageInfo(context); + if (packageInfo != null) { + return (int) PackageInfoCompat.getLongVersionCode(packageInfo); + } + + return 0; + } + + public static String getAppVersionName(Context context) { + PackageInfo packageInfo = createPackageInfo(context); + if (packageInfo != null) { + return packageInfo.versionName; + } + + return null; + } + + private static PackageInfo createPackageInfo(Context context) { + try { + return context + .getPackageManager() + .getPackageInfo(context.getPackageName(), 0); + } catch (NameNotFoundException e) { + Log.d(TAG, e.getMessage()); + e.printStackTrace(); + } + + return null; + } + + public static String getActivityLabel(Context context) { + return getActivityLabel(context, (String) null); + } + + public static String getActivityLabel(Context context, String cls) { + if (cls != null) { + return getActivityLabel(context, new ComponentName(context, cls)); + } else if (context instanceof Activity) { + Activity activity = (Activity) context; + return getActivityLabel(context, activity.getComponentName()); + } + + return null; + } + + private static String getActivityLabel(Context context, ComponentName name) { + PackageManager pm = context.getPackageManager(); + + ActivityInfo info = null; + + try { + info = pm.getActivityInfo(name, 0); + return context.getResources().getString(info.labelRes); + } catch (NameNotFoundException | NotFoundException e) { + if (info != null) { + return Helpers.getSimpleClassName(info.name); // label not found, return simple class name + } + } + + return null; + } + + public static ActivityInfo getActivityInfo(Context ctx, ComponentName componentName) { + ActivityInfo ai = null; + try { + ai = ctx.getPackageManager().getActivityInfo(componentName, PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return ai; + } + + public static ProviderInfo getProviderInfo(Context ctx, ComponentName componentName) { + ProviderInfo ai = null; + try { + ai = ctx.getPackageManager().getProviderInfo(componentName, PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return ai; + } + + public static ActivityInfo[] getActivityList(Context context) { + PackageManager pm = context.getPackageManager(); + + ActivityInfo[] list = null; + + try { + PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); + list = info.activities; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + + return list; + } + + public static boolean isActivityExists(Context context, String actName) { + ActivityInfo[] list = getActivityList(context); + + if (list != null) { + for (ActivityInfo activityInfo : list) { + if (activityInfo.name.contains(actName)) { + return true; + } + } + } + + return false; + } + + public static String getApplicationName(Context context) { + if (context == null) { + return null; + } + + ApplicationInfo applicationInfo = context.getApplicationInfo(); + int stringId = applicationInfo.labelRes; + return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/Helpers.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/Helpers.java new file mode 100644 index 0000000..2b3af34 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/Helpers.java @@ -0,0 +1,251 @@ +package com.liskovsoft.leankeyboard.helpers; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity; +import com.liskovsoft.leankeyboard.utils.LocaleUtility; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Helpers { + /** + * Simple wildcard matching routine. Implemented without regex. So you may expect huge performance boost. + * @param host + * @param mask + * @return + */ + public static boolean matchSubstr(String host, String mask) { + String[] sections = mask.split("\\*"); + String text = host; + for (String section : sections) { + int index = text.indexOf(section); + if (index == -1) { + return false; + } + text = text.substring(index + section.length()); + } + return true; + } + + public static boolean matchSubstrNoCase(String host, String mask) { + return matchSubstr(host.toLowerCase(), mask.toLowerCase()); + } + + public static InputStream getAsset(Context ctx, String fileName) { + InputStream is = null; + try { + is = ctx.getAssets().open(fileName); + } catch (IOException e) { + e.printStackTrace(); + } + return is; + } + + public static String encodeURI(byte[] data) { + try { + // make behaviour of java uri-encode the same as javascript's one + return URLEncoder.encode(new String(data, "UTF-8"), "UTF-8").replace("+", "%20"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static boolean floatEquals(float num1, float num2) { + float epsilon = 0.1f; + return Math.abs(num1 - num2) < epsilon; + } + + public static String getDeviceName() { + return String.format("%s (%s)", Build.MODEL, Build.PRODUCT); + } + + public static boolean deviceMatch(String[] devicesToProcess) { + String thisDeviceName = Helpers.getDeviceName(); + for (String deviceName : devicesToProcess) { + boolean match = matchSubstrNoCase(thisDeviceName, deviceName); + if (match) { + return true; + } + } + return false; + } + + public static String toString(Throwable ex) { + if (ex instanceof IllegalStateException && + ex.getCause() != null) { + ex = ex.getCause(); + } + return String.format("%s: %s", ex.getClass().getCanonicalName(), ex.getMessage()); + } + + public static String toString(InputStream inputStream) { + if (inputStream == null) { + return null; + } + + Scanner s = new Scanner(inputStream).useDelimiter("\\A"); + String result = s.hasNext() ? s.next() : ""; + return result; + } + + public static InputStream toStream(String content) { + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + } + + public static void postOnUiThread(Runnable runnable) { + new Handler(Looper.getMainLooper()).post(runnable); + } + + public static String unixToLocalDate(Context ctx, String timestamp) { + Locale current = LocaleUtility.getSystemLocale(ctx); + DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, current); + Date date; + if (timestamp == null) { + date = new Date(); + } else { + date = new Date((long) Integer.parseInt(timestamp) * 1000); + } + return dateFormat.format(date); + } + + public static String runMultiMatcher(String input, String... patterns) { + if (input == null) { + return null; + } + + Pattern regex; + Matcher matcher; + String result = null; + for (String pattern : patterns) { + regex = Pattern.compile(pattern); + matcher = regex.matcher(input); + + if (matcher.find()) { + result = matcher.group(matcher.groupCount()); // get last group + break; + } + } + + return result; + } + + public static boolean isCallable(Context ctx, Intent intent) { + List list = ctx.getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + + public static String getSimpleClassName(String name) { + if (name == null) { + return null; + } + + return name.substring(name.lastIndexOf('.') + 1); + } + + private static 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 static 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); + } + + public static Class classForName(String clazz) { + Class serviceClass; + try { + serviceClass = Class.forName(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return serviceClass; + } + + public static String getPackageName(Context ctx) { + return ctx.getPackageName(); + } + + public static void startActivity(Context context, Class activityClass) { + try { + Intent intent = new Intent(context, activityClass); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + MessageHelpers.showLongMessage(context, "Can't start: " + e.getMessage()); + } + } + + public static boolean startIntent(final Context context, final Intent intent) { + if (intent == null) { + return false; + } + + try { + context.startActivity(intent); + } catch (ActivityNotFoundException ex) { + return false; + } + + return true; + } + + public static boolean isGenymotion() { + String deviceName = getDeviceName(); + + return deviceName.contains("(vbox86p)"); + } + + public static void setLauncherIconShown(Context context, Class activityClass, boolean shown) { + PackageManager pm = context.getPackageManager(); + ComponentName component = new ComponentName(context, activityClass); + pm.setComponentEnabledSetting( + component, + shown ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ); + } + + public static boolean getLauncherIconShown(Context context, Class activityClass) { + PackageManager pm = context.getPackageManager(); + ComponentName component = new ComponentName(context, activityClass); + return pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/MessageHelpers.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/MessageHelpers.java new file mode 100644 index 0000000..119968f --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/MessageHelpers.java @@ -0,0 +1,90 @@ +package com.liskovsoft.leankeyboard.helpers; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; + +public class MessageHelpers { + private static long sExitMsgTimeMS = 0; + private static final int LONG_MSG_TIMEOUT = 5000; + + public static void showMessage(final Context ctx, final String TAG, final Throwable ex) { + showMessage(ctx, TAG, Helpers.toString(ex)); + } + + public static void showMessage(final Context ctx, final String TAG, final String msg) { + showMessage(ctx, String.format("%s: %s", TAG, msg)); + } + + public static void showMessageThrottled(final Context ctx, final String msg) { + // throttle msg calls + if (System.currentTimeMillis() - sExitMsgTimeMS < LONG_MSG_TIMEOUT) { + return; + } + sExitMsgTimeMS = System.currentTimeMillis(); + showMessage(ctx, msg); + } + + public static void showMessage(final Context ctx, final String msg) { + if (ctx == null) { + return; + } + + Runnable toast = () -> { + try { + Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show(); + } catch (Exception ex) { // NPE fix + ex.printStackTrace(); + } + }; + + if (Looper.myLooper() == Looper.getMainLooper()) { + toast.run(); + } else { + new Handler(Looper.getMainLooper()).post(toast); + } + } + + /** + * Shows long toast message.
+ * Uses resource id as message. + * @param ctx context + * @param resId resource id + */ + public static void showLongMessage(Context ctx, int resId) { + showLongMessage(ctx, ctx.getResources().getString(resId)); + } + + public static void showLongMessage(Context ctx, String msg) { + for (int i = 0; i < 3; i++) { + showMessage(ctx, msg); + } + } + + public static void showLongMessage(Context ctx, String TAG, String msg) { + for (int i = 0; i < 3; i++) { + showMessage(ctx, TAG, msg); + } + } + + /** + * Shows toast message.
+ * Uses resource id as message. + * @param ctx context + * @param resId resource id + */ + public static void showMessage(Context ctx, int resId) { + showMessage(ctx, ctx.getResources().getString(resId)); + } + + public static void showLongMessageEndPause(Context context, int resId) { + showLongMessage(context, resId); + + try { + Thread.sleep(5_000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/PermissionHelpers.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/PermissionHelpers.java new file mode 100644 index 0000000..59de2a6 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/helpers/PermissionHelpers.java @@ -0,0 +1,93 @@ +package com.liskovsoft.leankeyboard.helpers; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build.VERSION; +import androidx.core.app.ActivityCompat; + +@TargetApi(16) +public class PermissionHelpers { + // Storage Permissions + public static final int REQUEST_EXTERNAL_STORAGE = 112; + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + }; + + // Mic Permissions + public static final int REQUEST_MIC = 113; + private static String[] PERMISSIONS_MIC = { + Manifest.permission.RECORD_AUDIO + }; + + /** + * Checks if the app has permission to write to device storage
+ * If the app does not has permission then the user will be prompted to grant permissions
+ * Required for the {@link Context#getExternalCacheDir()}
+ * NOTE: runs async
+ * + * @param activity to apply permissions to + */ + public static void verifyStoragePermissions(Context activity) { + requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); + } + + public static void verifyMicPermissions(Context activity) { + requestPermissions(activity, PERMISSIONS_MIC, REQUEST_MIC); + } + + /** + * Only check. There is no prompt. + * @param activity to apply permissions to + * @return whether permission already granted + */ + public static boolean hasStoragePermissions(Context activity) { + // Check if we have write permission + return hasPermissions(activity, PERMISSIONS_STORAGE); + } + + public static boolean hasMicPermissions(Context activity) { + // Check if we have mic permission + return hasPermissions(activity, PERMISSIONS_MIC); + } + + // Utils + + /** + * Shows permissions dialog
+ * NOTE: runs async + */ + private static void requestPermissions(Context activity, String[] permissions, int requestId) { + if (!hasPermissions(activity, permissions) && !Helpers.isGenymotion()) { + if (activity instanceof Activity) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions( + (Activity) activity, + permissions, + requestId + ); + } + } + } + + /** + * Only check. There is no prompt. + * @param activity to apply permissions to + * @return whether permission already granted + */ + private static boolean hasPermissions(Context activity, String... permissions) { + if (VERSION.SDK_INT >= 23) { + for (String permission : permissions) { + int result = ActivityCompat.checkSelfPermission(activity, permission); + if (result != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + } + + return true; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/EventLogTags.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/EventLogTags.java new file mode 100644 index 0000000..465eb07 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/EventLogTags.java @@ -0,0 +1,16 @@ +package com.liskovsoft.leankeyboard.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 time, long duration) { + EventLog.writeEvent(TIME_LEANBACK_IME_INPUT, new Object[]{time, duration}); + } + + public static void writeTotalLeanbackImeBackspace(int count) { + EventLog.writeEvent(TOTAL_LEANBACK_IME_BACKSPACE, count); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/KeyMapperImeService.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/KeyMapperImeService.java new file mode 100644 index 0000000..3ada986 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/KeyMapperImeService.java @@ -0,0 +1,107 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.inputmethodservice.InputMethodService; +import android.os.Build; +import android.os.Build.VERSION; +import android.view.KeyEvent; +import android.view.inputmethod.InputConnection; +import com.liskovsoft.leankeykeyboard.BuildConfig; + +public class KeyMapperImeService extends InputMethodService { + private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_DOWN_UP"; + private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_DOWN"; + private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_UP"; + private static final String KEY_MAPPER_INPUT_METHOD_ACTION_TEXT = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_TEXT"; + private static final String KEY_MAPPER_INPUT_METHOD_EXTRA_TEXT = BuildConfig.APPLICATION_ID + ".inputmethod.EXTRA_TEXT"; + private static final String KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT = BuildConfig.APPLICATION_ID + ".inputmethod.EXTRA_KEY_EVENT"; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + + String action = intent.getAction(); + InputConnection currentInputConnection = getCurrentInputConnection(); + + if (currentInputConnection == null || action == null) { + return; + } + + KeyEvent downEvent; + KeyEvent upEvent; + + switch (action) { + case KEY_MAPPER_INPUT_METHOD_ACTION_TEXT: + String text = intent.getStringExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_TEXT); + if (text == null) { + return; + } + + currentInputConnection.commitText(text, 1); + break; + case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP: + downEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT); + if (downEvent == null) { + return; + } + + currentInputConnection.sendKeyEvent(downEvent); + + upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + currentInputConnection.sendKeyEvent(upEvent); + break; + case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN: + downEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT); + if (downEvent == null) { + return; + } + + downEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_DOWN); + + currentInputConnection.sendKeyEvent(downEvent); + break; + case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP: + upEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT); + if (upEvent == null) { + return; + } + + upEvent = KeyEvent.changeAction(upEvent, KeyEvent.ACTION_UP); + + currentInputConnection.sendKeyEvent(upEvent); + break; + } + } + }; + + @SuppressWarnings("UnspecifiedRegisterReceiverFlag") + @Override + public void onCreate() { + super.onCreate(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN); + intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP); + intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP); + intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_TEXT); + + if (VERSION.SDK_INT < 33) { + registerReceiver(mBroadcastReceiver, intentFilter); + } else { + registerReceiver(mBroadcastReceiver, intentFilter, RECEIVER_EXPORTED); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + unregisterReceiver(mBroadcastReceiver); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackImeService.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackImeService.java new file mode 100644 index 0000000..cb39d57 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackImeService.java @@ -0,0 +1,403 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.inputmethodservice.InputMethodService; +import android.os.Build.VERSION; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.InputDevice; +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 androidx.core.text.BidiFormatter; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardController.InputListener; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; + +public class LeanbackImeService extends KeyMapperImeService { + private static final String TAG = LeanbackImeService.class.getSimpleName(); + 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 boolean mEnterSpaceBeforeCommitting; + private View mInputView; + private LeanbackKeyboardController mKeyboardController; + private boolean mShouldClearSuggestions = true; + private LeanbackSuggestionsFactory mSuggestionsFactory; + public static final String COMMAND_RESTART = "restart"; + private boolean mForceShowKbd; + + @SuppressLint("HandlerLeak") + private final Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + if (msg.what == MSG_SUGGESTIONS_CLEAR && mShouldClearSuggestions) { + mSuggestionsFactory.clearSuggestions(); + mKeyboardController.updateSuggestions(mSuggestionsFactory.getSuggestions()); + mShouldClearSuggestions = false; + } + + } + }; + + private InputListener mInputListener = this::handleTextEntry; + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public LeanbackImeService() { + if (VERSION.SDK_INT < 21 && !enableHardwareAcceleration()) { + Log.w("LbImeService", "Could not enable hardware acceleration"); + } + } + + @Override + public void onCreate() { + //setupDensity(); + super.onCreate(); + + Log.d(TAG, "onCreate"); + + initSettings(); + } + + private void setupDensity() { + if (LeanKeyPreferences.instance(this).getEnlargeKeyboard()) { + DisplayMetrics metrics = LeanbackUtils.createMetricsFrom(this, 1.3f); + + if (metrics != null) { + getResources().getDisplayMetrics().setTo(metrics); + } + } + } + + private void initSettings() { + LeanKeyPreferences prefs = LeanKeyPreferences.instance(this); + mForceShowKbd = prefs.getForceShowKeyboard(); + + if (mKeyboardController != null) { + mKeyboardController.setSuggestionsEnabled(prefs.getSuggestionsEnabled()); + } + } + + private void clearSuggestionsDelayed() { + if (!mSuggestionsFactory.shouldSuggestionsAmend()) { + mHandler.removeMessages(MSG_SUGGESTIONS_CLEAR); + mShouldClearSuggestions = true; + mHandler.sendEmptyMessageDelayed(MSG_SUGGESTIONS_CLEAR, SUGGESTIONS_CLEAR_DELAY); + } + + } + + private void handleTextEntry(final int type, final int keyCode, final CharSequence text) { + final InputConnection connection = getCurrentInputConnection(); + if (connection != null) { + boolean updateSuggestions; + switch (type) { + case InputListener.ENTRY_TYPE_STRING: + clearSuggestionsDelayed(); + if (mEnterSpaceBeforeCommitting && mKeyboardController.enableAutoEnterSpace()) { + if (LeanbackUtils.isAlphabet(keyCode)) { + connection.commitText(" ", 1); + } + + mEnterSpaceBeforeCommitting = false; + } + + connection.commitText(text, 1); + updateSuggestions = true; + if (keyCode == LeanbackKeyboardView.ASCII_PERIOD) { + mEnterSpaceBeforeCommitting = true; + } + break; + case InputListener.ENTRY_TYPE_BACKSPACE: + clearSuggestionsDelayed(); + connection.deleteSurroundingText(1, 0); + mEnterSpaceBeforeCommitting = false; + updateSuggestions = true; + break; + case InputListener.ENTRY_TYPE_SUGGESTION: + case InputListener.ENTRY_TYPE_VOICE: + clearSuggestionsDelayed(); + if (!mSuggestionsFactory.shouldSuggestionsAmend()) { + connection.deleteSurroundingText(LeanbackUtils.getCharLengthBeforeCursor(connection), LeanbackUtils.getCharLengthAfterCursor(connection)); + } else { + int location = LeanbackUtils.getAmpersandLocation(connection); + connection.setSelection(location, location); + connection.deleteSurroundingText(0, LeanbackUtils.getCharLengthAfterCursor(connection)); + } + + connection.commitText(text, 1); + mEnterSpaceBeforeCommitting = true; + case InputListener.ENTRY_TYPE_ACTION: // User presses Go, Send, Search etc + boolean result = sendDefaultEditorAction(true); + + if (result) { + hideWindow(); // SmartYouTubeTV: hide kbd on search page fix + } else { + LeanbackUtils.sendEnterKey(connection); + } + + updateSuggestions = false; + break; + case InputListener.ENTRY_TYPE_LEFT: + case InputListener.ENTRY_TYPE_RIGHT: + BidiFormatter formatter = BidiFormatter.getInstance(); + + CharSequence textBeforeCursor = connection.getTextBeforeCursor(1000, 0); + int lenBefore = 0; + boolean isRtlBefore = false; + //int rtlLenBefore = 0; + if (textBeforeCursor != null) { + lenBefore = textBeforeCursor.length(); + isRtlBefore = formatter.isRtl(textBeforeCursor); + //rtlLenBefore = LeanbackUtils.getRtlLenBeforeCursor(textBeforeCursor); + } + + CharSequence textAfterCursor = connection.getTextAfterCursor(1000, 0); + int lenAfter = 0; + //int rtlLenAfter = 0; + boolean isRtlAfter = false; + if (textAfterCursor != null) { + lenAfter = textAfterCursor.length(); + isRtlAfter = formatter.isRtl(textAfterCursor); + //rtlLenAfter = LeanbackUtils.getRtlLenAfterCursor(textAfterCursor); + } + + int index = lenBefore; + if (type == InputListener.ENTRY_TYPE_LEFT) { + if (lenBefore > 0) { + if (!isRtlBefore) { + index = lenBefore - 1; + } else { + if (lenAfter == 0) { + index = 1; + } else if (lenAfter == 1) { + index = 0; + } else { + index = lenBefore + 1; + } + } + } + + //Log.d(TAG, String.format("direction key: before: lenBefore=%s, lenAfter=%s, rtlLenBefore=%s, rtlLenAfter=%s", lenBefore, lenAfter, rtlLenBefore, rtlLenAfter)); + Log.d(TAG, String.format("direction key: before: lenBefore=%s, lenAfter=%s, isRtlBefore=%s", lenBefore, lenAfter, isRtlBefore)); + } else { + if (lenAfter > 0) { + if (!isRtlAfter) { + index = lenBefore + 1; + } else { + if (lenBefore == 0) { + index = lenAfter - 1; + } else if (lenBefore == 1) { + index = lenAfter + 1; + } else { + index = lenBefore - 1; + } + } + } + + //Log.d(TAG, String.format("direction key: after: lenBefore=%s, lenAfter=%s, rtlLenBefore=%s, rtlLenAfter=%s", lenBefore, lenAfter, rtlLenBefore, rtlLenAfter)); + Log.d(TAG, String.format("direction key: after: lenBefore=%s, lenAfter=%s, isRtlAfter=%s", lenBefore, lenAfter, isRtlAfter)); + } + + Log.d(TAG, "direction key: index: " + index); + + connection.setSelection(index, index); + updateSuggestions = true; + break; + case InputListener.ENTRY_TYPE_DISMISS: + connection.performEditorAction(EditorInfo.IME_ACTION_NONE); + updateSuggestions = false; + break; + case InputListener.ENTRY_TYPE_VOICE_DISMISS: + connection.performEditorAction(EditorInfo.IME_ACTION_GO); + updateSuggestions = false; + break; + default: + updateSuggestions = true; + } + + if (mKeyboardController.areSuggestionsEnabled() && updateSuggestions) { + mKeyboardController.updateSuggestions(mSuggestionsFactory.getSuggestions()); + } + } + } + + @Override + public View onCreateInputView() { + mInputView = mKeyboardController.getView(); + mInputView.requestFocus(); + + return mInputView; + } + + @Override + public void onDisplayCompletions(CompletionInfo[] infos) { + if (mKeyboardController.areSuggestionsEnabled()) { + mShouldClearSuggestions = false; + mHandler.removeMessages(123); + mSuggestionsFactory.onDisplayCompletions(infos); + mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + } + + } + + @Override + public boolean onEvaluateFullscreenMode() { + return false; // don't change it (true shows edit dialog above kbd) + } + + /** + * At this point, decision whether to show kbd taking place
+ * More info + * @return whether to show kbd + */ + @Override + public boolean onEvaluateInputViewShown() { + Log.d(TAG, "onEvaluateInputViewShown"); + return mForceShowKbd || super.onEvaluateInputViewShown(); + } + + // FireTV fix + @Override + public boolean onShowInputRequested(int flags, boolean configChange) { + Log.d(TAG, "onShowInputRequested"); + return mForceShowKbd || super.onShowInputRequested(flags, configChange); + } + + @Override + public void onFinishInputView(boolean finishingInput) { + super.onFinishInputView(finishingInput); + sendBroadcast(new Intent(IME_CLOSE)); + mSuggestionsFactory.clearSuggestions(); + + // NOTE: Trying to fix kbd without UI bug (telegram) + reInitKeyboard(); + } + + @SuppressLint("NewApi") + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return isInputViewShown() && + (event.getSource() & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION && + mKeyboardController.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); + } + + @SuppressLint("WrongConstant") + public void hideIme() { + requestHideSelf(InputMethodService.BACK_DISPOSITION_DEFAULT); + } + + @Override + public void onInitializeInterface() { + mKeyboardController = new LeanbackKeyboardController(this, mInputListener); + mKeyboardController.setHideWhenPhysicalKeyboardUsed(!mForceShowKbd); + mEnterSpaceBeforeCommitting = false; + mSuggestionsFactory = new LeanbackSuggestionsFactory(this, MAX_SUGGESTIONS); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + //// DOESN'T WORK!!! + //// Hide keyboard on ESC key: https://github.com/yuliskov/SmartYouTubeTV/issues/142 + //event = mapEscToBack(event); + //keyCode = mapEscToBack(keyCode); + + // Hide keyboard on ESC key: https://github.com/yuliskov/SmartYouTubeTV/issues/142 + if (keyCode == KeyEvent.KEYCODE_ESCAPE) { + hideIme(); + return true; + } + + return isInputViewShown() && mKeyboardController.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + //// DOESN'T WORK!!! + //// Hide keyboard on ESC key: https://github.com/yuliskov/SmartYouTubeTV/issues/142 + //event = mapEscToBack(event); + //keyCode = mapEscToBack(keyCode); + + return isInputViewShown() && mKeyboardController.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + private KeyEvent mapEscToBack(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { + // pay attention, you must pass the same action + event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_BACK); + } + return event; + } + + private int mapEscToBack(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE) { + keyCode = KeyEvent.KEYCODE_BACK; + } + return keyCode; + } + + @Override + public int onStartCommand(final Intent intent, final int flags, final int startId) { + if (intent != null) { + Log.d(TAG, "onStartCommand: " + intent.toUri(0)); + + if (intent.getBooleanExtra(COMMAND_RESTART, false)) { + Log.d(TAG, "onStartCommand: trying to restart service"); + + reInitKeyboard(); + + return Service.START_REDELIVER_INTENT; + } + } + + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onStartInput(EditorInfo info, boolean restarting) { + super.onStartInput(info, restarting); + mEnterSpaceBeforeCommitting = false; + mSuggestionsFactory.onStartInput(info); + mKeyboardController.onStartInput(info); + } + + @Override + public void onStartInputView(EditorInfo info, boolean restarting) { + super.onStartInputView(info, restarting); + + mKeyboardController.onStartInputView(); + sendBroadcast(new Intent(IME_OPEN)); + if (mKeyboardController.areSuggestionsEnabled()) { + mSuggestionsFactory.createSuggestions(); + mKeyboardController.updateSuggestions(mSuggestionsFactory.getSuggestions()); + + // NOTE: FileManager+ rename item fix: https://t.me/LeanKeyboard/931 + // NOTE: Code below deletes text that has selection. + //InputConnection connection = getCurrentInputConnection(); + //if (connection != null) { + // String text = LeanbackUtils.getEditorText(connection); + // connection.deleteSurroundingText(LeanbackUtils.getCharLengthBeforeCursor(connection), LeanbackUtils.getCharLengthAfterCursor(connection)); + // connection.commitText(text, 1); + //} + } + } + + private void reInitKeyboard() { + initSettings(); + + if (mKeyboardController != null) { + mKeyboardController.initKeyboards(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardContainer.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardContainer.java new file mode 100644 index 0000000..4d4c69a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardContainer.java @@ -0,0 +1,1587 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.os.Build.VERSION; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.RecognizerIntent; +import android.speech.SpeechRecognizer; +import android.text.InputType; +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.view.inputmethod.InputConnection; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import androidx.core.content.ContextCompat; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardManager.KeyboardData; +import com.liskovsoft.leankeyboard.addons.theme.ThemeManager; +import com.liskovsoft.leankeyboard.addons.voice.RecognizerIntentWrapper; +import com.liskovsoft.leankeyboard.helpers.PermissionHelpers; +import com.liskovsoft.leankeyboard.activity.PermissionsActivity; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardController.InputListener; +import com.liskovsoft.leankeyboard.ime.voice.RecognizerView; +import com.liskovsoft.leankeyboard.ime.voice.SpeechLevelSource; +import com.liskovsoft.leankeyboard.activity.settings.KbLayoutActivity; +import com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity; +import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardManager; +import com.liskovsoft.leankeyboard.helpers.Helpers; +import com.liskovsoft.leankeyboard.helpers.MessageHelpers; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; +import java.util.List; +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); + public static final int DIRECTION_UP = 8; + public static final int DIRECTION_DOWN = 2; + public static final int DIRECTION_LEFT = 1; + public static final int DIRECTION_RIGHT = 4; + private Keyboard mAbcKeyboard; + 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 KeyFocus mCurrKeyInfo = new KeyFocus(); + private DismissListener mDismissListener; + private KeyFocus mDownKeyInfo = new 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 ImageView mKeySelector; + private Drawable mKeySelectorSquare; + private Drawable mKeySelectorStretched; + private ThemeManager mThemeManager; + private ScaleAnimation mSelectorAnimation; + private ValueAnimator mSelectorAnimator; + private SpeechLevelSource mSpeechLevelSource; + private SpeechRecognizer mSpeechRecognizer; + private RecognizerIntentWrapper mRecognizerIntentWrapper; + private LinearLayout mSuggestions; + private View mSuggestionsBg; + private HorizontalScrollView mSuggestionsContainer; + private boolean mSuggestionsEnabled; + private boolean mForceDisableSuggestions; + private Keyboard mSymKeyboard; + private KeyFocus mTempKeyInfo = new KeyFocus(); + private PointF mTempPoint = new PointF(); + private boolean mTouchDown = false; + private int mTouchState = TOUCH_STATE_NO_TOUCH; + private final int mVoiceAnimDur; + private final VoiceIntroAnimator mVoiceAnimator; + private RecognizerView mVoiceButtonView; + private boolean mVoiceEnabled; + private boolean mVoiceKeyDismissesEnabled; + private VoiceListener mVoiceListener; + private boolean mVoiceOn; + private Float mX; + private Float mY; + private String mLabel; + + private AnimatorListener mVoiceEnterListener = new AnimatorListener() { + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public void onAnimationStart(Animator animation) { + mSelector.setVisibility(View.INVISIBLE); + startRecognition(mContext); + } + }; + + private AnimatorListener mVoiceExitListener = new AnimatorListener() { + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mSelector.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public void onAnimationStart(Animator animation) { + mVoiceButtonView.showNotListening(); + mSpeechRecognizer.cancel(); + mSpeechRecognizer.setRecognitionListener(null); + mVoiceOn = false; + } + }; + + public LeanbackKeyboardContainer(Context context) { + mContext = (LeanbackImeService) context; + final Resources res = mContext.getResources(); + mVoiceAnimDur = res.getInteger(R.integer.voice_anim_duration); + mAlphaIn = res.getFraction(R.fraction.alpha_in, 1, 1); + mAlphaOut = res.getFraction(R.fraction.alpha_out, 1, 1); + mVoiceAnimator = new LeanbackKeyboardContainer.VoiceIntroAnimator(mVoiceEnterListener, mVoiceExitListener); + mRootView = (RelativeLayout) mContext.getLayoutInflater().inflate(R.layout.root_leanback, null); + mKeyboardsContainer = mRootView.findViewById(R.id.keyboard); + mSuggestionsBg = mRootView.findViewById(R.id.candidate_background); + mSuggestionsContainer = (HorizontalScrollView) mRootView.findViewById(R.id.suggestions_container); + mSuggestions = (LinearLayout) mSuggestionsContainer.findViewById(R.id.suggestions); + mMainKeyboardView = (LeanbackKeyboardView) mRootView.findViewById(R.id.main_keyboard); + mVoiceButtonView = (RecognizerView) mRootView.findViewById(R.id.voice); + mActionButtonView = (Button) mRootView.findViewById(R.id.enter); + mSelector = mRootView.findViewById(R.id.selector); + mKeySelector = mRootView.findViewById(R.id.key_selector); + mKeySelectorSquare = ContextCompat.getDrawable(mContext, R.drawable.key_selector_square); + mKeySelectorStretched = ContextCompat.getDrawable(mContext, R.drawable.key_selector); + mThemeManager = new ThemeManager(mContext, mRootView); + mSelectorAnimation = new ScaleAnimation((FrameLayout) mSelector); + mOverestimate = mContext.getResources().getFraction(R.fraction.focused_scale, 1, 1); + final float scale = context.getResources().getFraction(R.fraction.clicked_scale, 1, 1); + mClickAnimDur = context.getResources().getInteger(R.integer.clicked_anim_duration); + mSelectorAnimator = ValueAnimator.ofFloat(1.0F, scale); + mSelectorAnimator.setDuration(mClickAnimDur); + mSelectorAnimator.addUpdateListener(animation -> { + final float value = (Float) animation.getAnimatedValue(); + mSelector.setScaleX(value); + mSelector.setScaleY(value); + }); + mSpeechLevelSource = new SpeechLevelSource(); + mVoiceButtonView.setSpeechLevelSource(mSpeechLevelSource); + mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(mContext); + mRecognizerIntentWrapper = new RecognizerIntentWrapper(mContext); + mVoiceButtonView.setCallback(new RecognizerView.Callback() { + @Override + public void onCancelRecordingClicked() { + cancelVoiceRecording(); + } + + @Override + public void onStartRecordingClicked() { + startVoiceRecording(); + } + + @Override + public void onStopRecordingClicked() { + cancelVoiceRecording(); + } + }); + mKeyboardManager = new KeyboardManager(mContext); + initKeyboards(); + } + + private void configureFocus(KeyFocus focus, Rect rect, int index, int type) { + focus.type = type; + focus.index = index; + focus.rect.set(rect); + } + + /** + * NOTE: Initialize {@link KeyFocus} with values + * @param focus {@link KeyFocus} to configure + * @param rect {@link Rect} + * @param index key index + * @param key {@link Key} + * @param type {@link KeyFocus#type} constant + */ + private void configureFocus(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() { + mDismissListener.onDismiss(false); + } + + private PointF getAlignmentPosition(final float posXCm, final float posYCm, final PointF result) { + final float width = (float) (mRootView.getWidth() - mRootView.getPaddingRight() - mRootView.getPaddingLeft()); + final float height = (float) (mRootView.getHeight() - mRootView.getPaddingTop() - mRootView.getPaddingBottom()); + final float size = mContext.getResources().getDimension(R.dimen.selector_size); + result.x = posXCm / PHYSICAL_WIDTH_CM * (width - size) + (float) mRootView.getPaddingLeft(); + result.y = posYCm / PHYSICAL_HEIGHT_CM * (height - size) + (float) mRootView.getPaddingTop(); + return result; + } + + private void getPhysicalPosition(final float x, final float y, final PointF position) { + float width = (float) (mSelector.getWidth() / 2); + float height = (float) (mSelector.getHeight() / 2); + float posXCm = (float) (mRootView.getWidth() - mRootView.getPaddingRight() - mRootView.getPaddingLeft()); + float posYCm = (float) (mRootView.getHeight() - mRootView.getPaddingTop() - mRootView.getPaddingBottom()); + float size = mContext.getResources().getDimension(R.dimen.selector_size); + position.x = (x - width - (float) mRootView.getPaddingLeft()) * PHYSICAL_WIDTH_CM / (posXCm - size); + position.y = (y - height - (float) mRootView.getPaddingTop()) * PHYSICAL_HEIGHT_CM / (posYCm - size); + } + + private PointF getTouchSnapPosition() { + PointF position = new PointF(); + getPhysicalPosition((float) mCurrKeyInfo.rect.centerX(), (float) mCurrKeyInfo.rect.centerY(), position); + return position; + } + + public void initKeyboards() { + updateAddonKeyboard(); + } + + 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; + } + + /** + * NOTE: Move focus to specified key + * @param index key index + * @param type {@link KeyFocus#type} constant + */ + private void moveFocusToIndex(int index, int type) { + Key key = mMainKeyboardView.getKey(index); + configureFocus(mTempKeyInfo, mRect, index, key, type); + setTouchState(TOUCH_STATE_NO_TOUCH); + setKbFocus(mTempKeyInfo, true, true); + } + + private void offsetRect(Rect rect, View view) { + rect.left = 0; + rect.top = 0; + rect.right = view.getWidth(); + rect.bottom = view.getHeight(); + mRootView.offsetDescendantRectToMyCoords(view, rect); + } + + private void onToggleCapsLock() { + onShiftDoubleClick(isCapsLockOn()); + } + + /** + * NOTE: Init currently displayed keyboard
+ * All keyboard settings applied here
+ * This method is called constantly on new field + * @param res resources (not used) + * @param info current ime attributes + */ + private void setImeOptions(Resources res, EditorInfo info) { + // do not erase last keyboard + if (mInitialMainKeyboard == null) { + mInitialMainKeyboard = mAbcKeyboard; + } + + mSuggestionsEnabled = true; + mAutoEnterSpaceEnabled = false; + mVoiceEnabled = true; + mEscapeNorthEnabled = false; + mVoiceKeyDismissesEnabled = false; + + switch (LeanbackUtils.getInputTypeClass(info)) { + case InputType.TYPE_CLASS_TEXT: + switch (LeanbackUtils.getInputTypeVariation(info)) { + case InputType.TYPE_DATETIME_VARIATION_DATE: + case InputType.TYPE_DATETIME_VARIATION_TIME: + case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT: + case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS: + mSuggestionsEnabled = true; + mAutoEnterSpaceEnabled = false; + mVoiceEnabled = true; + mInitialMainKeyboard = mAbcKeyboard; + break; + case InputType.TYPE_TEXT_VARIATION_PERSON_NAME: + case InputType.TYPE_TEXT_VARIATION_PASSWORD: + case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD: + case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD: + mSuggestionsEnabled = true; // use suggestion widget as input indicator + mVoiceEnabled = false; + mInitialMainKeyboard = mAbcKeyboard; + } + break; + case InputType.TYPE_CLASS_NUMBER: + case InputType.TYPE_CLASS_PHONE: + case InputType.TYPE_CLASS_DATETIME: + mSuggestionsEnabled = true; + mVoiceEnabled = false; + mInitialMainKeyboard = mAbcKeyboard; + } + + if (mSuggestionsEnabled) { + if ((info.inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { + mSuggestionsEnabled = false; + } + } + + // NOTE: bug fix: any field: first char in upper case + //if (!mAutoEnterSpaceEnabled) { + // if ((info.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { + // mCapSentences = true; + // } + //} + + if (mAutoEnterSpaceEnabled && !mSuggestionsEnabled) { + mAutoEnterSpaceEnabled = false; + } + + // NOTE: bug fix: any field: first char in upper case + //if ((info.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0 || + // LeanbackUtils.getInputTypeVariation(info) == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { + // mCapWords = true; + //} + + // NOTE: bug fix: password field: all chars in upper case + //if ((info.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { + // mCapCharacters = true; + //} + + if (mForceDisableSuggestions) { + mSuggestionsEnabled = false; + } + + if (info.privateImeOptions != null) { + if (info.privateImeOptions.contains(IME_PRIVATE_OPTIONS_ESCAPE_NORTH)) { + mEscapeNorthEnabled = true; + } + + if (info.privateImeOptions.contains(IME_PRIVATE_OPTIONS_VOICE_DISMISS)) { + mVoiceKeyDismissesEnabled = true; + } + } + + mEnterKeyText = info.actionLabel; + if (TextUtils.isEmpty(mEnterKeyText)) { + switch (LeanbackUtils.getImeAction(info)) { + case EditorInfo.IME_ACTION_GO: + mEnterKeyTextResId = R.string.label_go_key; + return; + case EditorInfo.IME_ACTION_SEARCH: + mEnterKeyTextResId = R.string.label_search_key; + return; + case EditorInfo.IME_ACTION_SEND: + mEnterKeyTextResId = R.string.label_send_key; + return; + case EditorInfo.IME_ACTION_NEXT: + mEnterKeyTextResId = R.string.label_next_key; + return; + default: + mEnterKeyTextResId = R.string.label_done_key; + } + } + + } + + /** + * Move focus to specified key + * @param focus key that will be focused + * @param forceFocusChange force focus + * @param animate animate transition + */ + private void setKbFocus(final KeyFocus focus, final boolean forceFocusChange, final boolean animate) { + boolean clicked = true; + if (!focus.equals(mCurrKeyInfo) || forceFocusChange) { + LeanbackKeyboardView prevView = mPrevView; + mPrevView = null; + boolean overestimateWidth = false; + boolean overestimateHeight = false; + switch (focus.type) { + case KeyFocus.TYPE_MAIN: + boolean showScale = false; + overestimateHeight = true; + if (focus.code != LeanbackKeyboardView.ASCII_SPACE) { + overestimateWidth = true; + showScale = true; + } + + LeanbackKeyboardView mainView = mMainKeyboardView; + int index = focus.index; + + boolean isClicked = false; + if (mTouchState == TOUCH_STATE_CLICK) { + isClicked = true; + } + + mainView.setFocus(index, isClicked, showScale); + mPrevView = mMainKeyboardView; + break; + case KeyFocus.TYPE_VOICE: + mVoiceButtonView.setMicFocused(true); + dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_ACTION: + LeanbackUtils.sendAccessibilityEvent(mActionButtonView, true); + dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_SUGGESTION: + dismissMiniKeyboard(); + } + + if (prevView != null && prevView != mPrevView) { + if (mTouchState != TOUCH_STATE_CLICK) { + clicked = false; + } + + prevView.setFocus(-1, clicked); + } + + setSelectorToFocus(focus.rect, overestimateWidth, overestimateHeight, animate); + mCurrKeyInfo.set(focus); + } + } + + /** + * Set keyboard shift sate + * @param state one of the + * {@link LeanbackKeyboardView#SHIFT_ON SHIFT_ON}, + * {@link LeanbackKeyboardView#SHIFT_OFF SHIFT_OFF}, + * {@link LeanbackKeyboardView#SHIFT_LOCKED SHIFT_LOCKED} + * constants + */ + private void setShiftState(int state) { + mMainKeyboardView.setShiftState(state); + } + + private void setTouchStateInternal(int state) { + mTouchState = state; + } + + /** + * NOTE: Speech recognizer routine + * @param context context + */ + private void startRecognition(Context context) { + // MANAGE_EXTERNAL_STORAGE does not work on Android 14 + if ((PermissionHelpers.hasStoragePermissions(context) || VERSION.SDK_INT >= 34) && + PermissionHelpers.hasMicPermissions(context)) { + if (SpeechRecognizer.isRecognitionAvailable(context)) { + mRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + mRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + mRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); + mSpeechRecognizer.setRecognitionListener(new MyVoiceRecognitionListener()); + + mSpeechRecognizer.startListening(mRecognizerIntent); + } else { + mRecognizerIntentWrapper.setListener(searchText -> mVoiceListener.onVoiceResult(searchText)); + mRecognizerIntentWrapper.startListening(); + + //String noRecognition = "Seems that the voice recognition is not enabled on your device"; + // + //MessageHelpers.showLongMessage(context, noRecognition); + // + //Log.e(TAG, noRecognition); + } + } else { + Helpers.startActivity(context, PermissionsActivity.class); + } + } + + public void alignSelector(final float x, final float y, final boolean playAnimation) { + final float translatedX = x - (float) (mSelector.getWidth() / 2); + final float translatedY = y - (float) (mSelector.getHeight() / 2); + if (!playAnimation) { + mSelector.setX(translatedX); + mSelector.setY(translatedY); + } else { + mSelector.animate() + .x(translatedX) + .y(translatedY) + .setInterpolator(sMovementInterpolator) + .setDuration(MOVEMENT_ANIMATION_DURATION) + .start(); + } + } + + public boolean areSuggestionsEnabled() { + return mSuggestionsEnabled; + } + + public void setSuggestionsEnabled(boolean enabled) { + mSuggestionsEnabled = enabled; + mForceDisableSuggestions = !enabled; + } + + public void cancelVoiceRecording() { + mVoiceAnimator.startExitAnimation(); + } + + public void clearSuggestions() { + mSuggestions.removeAllViews(); + if (getCurrFocus().type == KeyFocus.TYPE_SUGGESTION) { + resetFocusCursor(); + } + } + + public boolean dismissMiniKeyboard() { + return mMainKeyboardView.dismissMiniKeyboard(); + } + + public boolean enableAutoEnterSpace() { + return mAutoEnterSpaceEnabled; + } + + /** + * Initialize {@link KeyFocus focus} variable based on supplied coordinates + * @param x x coordinates + * @param y y coordinates + * @param focus result focus + * @return whether focus is found or not + */ + public boolean getBestFocus(final Float x, final Float y, final KeyFocus focus) { + offsetRect(mRect, mActionButtonView); + int actionLeft = mRect.left; + offsetRect(mRect, mMainKeyboardView); + int keyboardTop = mRect.top; + Float newX = x; + if (x == null) { + newX = mX; + } + + Float newY = y; + if (y == null) { + newY = mY; + } + + int count = mSuggestions.getChildCount(); + if (newY < (float) keyboardTop && count > 0 && mSuggestionsEnabled) { + for (actionLeft = 0; actionLeft < count; ++actionLeft) { + View view = mSuggestions.getChildAt(actionLeft); + offsetRect(mRect, view); + if (newX < (float) mRect.right || actionLeft + 1 == count) { + view.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(view.findViewById(R.id.text), true); + configureFocus(focus, mRect, actionLeft, KeyFocus.TYPE_SUGGESTION); + break; + } + } + + return true; + } else if (newY < (float) keyboardTop && mEscapeNorthEnabled) { + escapeNorth(); + return false; + } else if (newX > (float) actionLeft) { + offsetRect(mRect, mActionButtonView); + configureFocus(focus, mRect, 0, KeyFocus.TYPE_ACTION); + return true; + } else { + mX = newX; + mY = newY; + offsetRect(mRect, mMainKeyboardView); + final float left = (float) mRect.left; + final float top = (float) mRect.top; + int keyIdx = mMainKeyboardView.getNearestIndex(newX - left, newY - top); + Key key = mMainKeyboardView.getKey(keyIdx); + configureFocus(focus, mRect, keyIdx, key, 0); + return true; + } + } + + public LeanbackKeyboardContainer.KeyFocus getCurrFocus() { + return mCurrKeyInfo; + } + + public int getCurrKeyCode() { + int keyCode = 0; + Key key = getKey(mCurrKeyInfo.type, mCurrKeyInfo.index); + if (key != null) { + keyCode = key.codes[0]; + } + + return keyCode; + } + + public Button getGoButton() { + return mActionButtonView; + } + + public Key getKey(int type, int index) { + return type == KeyFocus.TYPE_MAIN ? this.mMainKeyboardView.getKey(index) : null; + } + + public void updateCyclicFocus(int dir, KeyFocus oldFocus, KeyFocus newFocus) { + if (oldFocus.equals(newFocus) || LeanbackUtils.isSubmitButton(newFocus)) { + if (LeanKeyPreferences.instance(mContext).isCyclicNavigationEnabled()) { + if (dir == DIRECTION_RIGHT || dir == DIRECTION_LEFT) { + Rect actionRect = new Rect(); + offsetRect(actionRect, mActionButtonView); + boolean onSameRow = Math.abs(oldFocus.rect.top - actionRect.top) < 20; + + if (onSameRow && !LeanbackUtils.isSubmitButton(oldFocus)) { + // move focus to submit button + offsetRect(mRect, mActionButtonView); + configureFocus(newFocus, mRect, 0, KeyFocus.TYPE_ACTION); + } else { + offsetRect(mRect, mMainKeyboardView); + float x = dir == DIRECTION_RIGHT ? 0 : mRect.right; // 0 - rightmost position, right - leftmost + int keyIdx = mMainKeyboardView.getNearestIndex(x, oldFocus.rect.top - mRect.top); + Key key = mMainKeyboardView.getKey(keyIdx); + configureFocus(newFocus, mRect, keyIdx, key, 0); + } + } else if (dir == DIRECTION_DOWN || dir == DIRECTION_UP) { + if (!LeanbackUtils.isSubmitButton(oldFocus)) { + offsetRect(mRect, mMainKeyboardView); + float y = dir == DIRECTION_DOWN ? 0 : mRect.bottom; // 0 - topmost position, bottom - downmost + int delta = (oldFocus.rect.right - oldFocus.rect.left) / 2; // fix space position + int keyIdx = mMainKeyboardView.getNearestIndex(oldFocus.rect.left + delta - mRect.left, y); + Key key = mMainKeyboardView.getKey(keyIdx); + configureFocus(newFocus, mRect, keyIdx, key, 0); + } + } + } else if (dir == DIRECTION_UP) { + // Hide the keyboard when moving focus out of the keyboard + mContext.hideIme(); + } + + String direction = "UNKNOWN"; + + switch (dir) { + case LeanbackKeyboardContainer.DIRECTION_DOWN: + direction = "DOWN"; + break; + case LeanbackKeyboardContainer.DIRECTION_LEFT: + direction = "LEFT"; + break; + case LeanbackKeyboardContainer.DIRECTION_RIGHT: + direction = "RIGHT"; + break; + case LeanbackKeyboardContainer.DIRECTION_UP: + direction = "UP"; + break; + } + + Log.d(TAG, "Same key focus found! Direction: " + direction + " Key Label: " + oldFocus.label); + } + } + + public boolean getNextFocusInDirection(int direction, KeyFocus startFocus, KeyFocus nextFocus) { + switch (startFocus.type) { + case KeyFocus.TYPE_MAIN: + Key key = getKey(startFocus.type, startFocus.index); + float centerDelta = (float) startFocus.rect.height() / 2.0F; + float centerX = (float) startFocus.rect.centerX(); + float centerY = (float) startFocus.rect.centerY(); + if (startFocus.code == LeanbackKeyboardView.ASCII_SPACE) { + centerX = mX; + } + + if ((direction & DIRECTION_LEFT) != 0) { + if ((key.edgeFlags & Keyboard.EDGE_LEFT) == 0) { + centerX = (float) startFocus.rect.left - centerDelta; + } + } else if ((direction & DIRECTION_RIGHT) != 0) { + if ((key.edgeFlags & Keyboard.EDGE_RIGHT) != 0) { + offsetRect(mRect, mActionButtonView); + centerX = (float) mRect.centerX(); + } else { + centerX = (float) startFocus.rect.right + centerDelta; + } + } + + if ((direction & DIRECTION_UP) != 0) { + centerDelta = (float) ((double) centerY - (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } else { + centerDelta = centerY; + if ((direction & DIRECTION_DOWN) != 0) { + centerDelta = (float) ((double) centerY + (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } + } + + getPhysicalPosition(centerX, centerDelta, mTempPoint); + return getBestFocus(centerX, centerDelta, nextFocus); + case KeyFocus.TYPE_VOICE: + default: + break; + case KeyFocus.TYPE_ACTION: + offsetRect(mRect, mMainKeyboardView); + if ((direction & DIRECTION_LEFT) != 0) { + return getBestFocus((float) mRect.right, null, nextFocus); + } + + if ((direction & DIRECTION_UP) != 0) { + offsetRect(mRect, mSuggestions); + return getBestFocus((float) startFocus.rect.centerX(), (float) mRect.centerY(), nextFocus); + } + break; + case KeyFocus.TYPE_SUGGESTION: + if ((direction & DIRECTION_DOWN) != 0) { + offsetRect(mRect, mMainKeyboardView); + return getBestFocus((float) startFocus.rect.centerX(), (float) mRect.top, nextFocus); + } + + if ((direction & DIRECTION_UP) != 0) { + if (mEscapeNorthEnabled) { + escapeNorth(); + return true; + } + } else { + boolean left = (direction & DIRECTION_LEFT) != 0; + + boolean right = (direction & DIRECTION_RIGHT) != 0; + + if (left || right) { + offsetRect(mRect, mRootView); + MarginLayoutParams params = (MarginLayoutParams) mSuggestionsContainer.getLayoutParams(); + int leftCalc = mRect.left + params.leftMargin; + int rightCalc = mRect.right - params.rightMargin; + int focusIdx = startFocus.index; + byte delta; + if (left) { + delta = -1; + } else { + delta = 1; + } + + int suggestIdx = focusIdx + delta; + View suggestion = mSuggestions.getChildAt(suggestIdx); + if (suggestion != null) { + offsetRect(mRect, suggestion); + if (mRect.left < leftCalc && mRect.right > rightCalc) { + mRect.left = leftCalc; + mRect.right = rightCalc; + } else if (mRect.left < leftCalc) { + mRect.right = mRect.width() + leftCalc; + mRect.left = leftCalc; + } else if (mRect.right > rightCalc) { + mRect.left = rightCalc - mRect.width(); + mRect.right = rightCalc; + } + + suggestion.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(suggestion.findViewById(R.id.text), true); + configureFocus(nextFocus, mRect, suggestIdx, KeyFocus.TYPE_SUGGESTION); + return true; + } + } + } + } + + return true; + } + + public CharSequence getSuggestionText(int idx) { + CharSequence result = null; + if (idx >= 0) { + if (idx < mSuggestions.getChildCount()) { + Button btn = mSuggestions.getChildAt(idx).findViewById(R.id.text); + if (btn != null) { + result = btn.getText(); + } + } + } + + return result; + } + + public int getTouchState() { + return mTouchState; + } + + public RelativeLayout getView() { + return mRootView; + } + + public boolean isCapsLockOn() { + return mMainKeyboardView.getShiftState() == LeanbackKeyboardView.SHIFT_LOCKED; + } + + public boolean isCurrKeyShifted() { + return mMainKeyboardView.isShifted(); + } + + public boolean isMiniKeyboardOnScreen() { + return mMainKeyboardView.isMiniKeyboardOnScreen(); + } + + public boolean isVoiceEnabled() { + return mVoiceEnabled; + } + + public boolean isVoiceVisible() { + return mVoiceButtonView.getVisibility() == View.VISIBLE; + } + + public void onInitInputView() { + resetFocusCursor(); + mSelector.setVisibility(View.VISIBLE); + } + + public boolean onKeyLongPress() { + int keyCode = mCurrKeyInfo.code; + if (keyCode == LeanbackKeyboardView.KEYCODE_SHIFT) { + onToggleCapsLock(); + setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_NO_TOUCH); + return true; + } else if (keyCode == LeanbackKeyboardView.ASCII_SPACE) { + LeanbackUtils.showKeyboardPicker(mContext); + // Keyboard may stuck on screen. Fixing it... + mContext.stopSelf(); + // Revert button touch states to normal + setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_NO_TOUCH); + return true; + } else if (keyCode == LeanbackKeyboardView.KEYCODE_LANG_TOGGLE) { + Helpers.startActivity(mContext, KbSettingsActivity.class); + mContext.hideIme(); + return true; + } else { + if (mCurrKeyInfo.type == KeyFocus.TYPE_MAIN) { + mMainKeyboardView.onKeyLongPress(); + if (mMainKeyboardView.isMiniKeyboardOnScreen()) { + mMiniKbKeyIndex = mCurrKeyInfo.index; + moveFocusToIndex(mMainKeyboardView.getBaseMiniKbIndex(), KeyFocus.TYPE_MAIN); + return true; + } + } + + return false; + } + } + + public void onModeChangeClick() { + dismissMiniKeyboard(); + if (mMainKeyboardView.getKeyboard().equals(mSymKeyboard)) { + mMainKeyboardView.setKeyboard(mInitialMainKeyboard); + } else { + mMainKeyboardView.setKeyboard(mSymKeyboard); + } + } + + public void onPeriodEntry() { + if (mMainKeyboardView.isShifted()) { + if (!isCapsLockOn() && !mCapCharacters && !mCapWords && !mCapSentences) { + setShiftState(LeanbackKeyboardView.SHIFT_OFF); + } + } else if (isCapsLockOn() || mCapCharacters || mCapWords || mCapSentences) { + setShiftState(LeanbackKeyboardView.SHIFT_ON); + } + } + + public void onShiftClick() { + byte state; + if (mMainKeyboardView.isShifted()) { + state = LeanbackKeyboardView.SHIFT_OFF; + } else { + state = LeanbackKeyboardView.SHIFT_ON; + } + + setShiftState(state); + } + + public void onShiftDoubleClick(boolean wasCapsLockOn) { + byte state; + if (wasCapsLockOn) { + state = LeanbackKeyboardView.SHIFT_OFF; + } else { + state = LeanbackKeyboardView.SHIFT_LOCKED; + } + + setShiftState(state); + } + + public void onSpaceEntry() { + if (mMainKeyboardView.isShifted()) { + if (!isCapsLockOn() && !mCapCharacters && !mCapWords) { + setShiftState(LeanbackKeyboardView.SHIFT_OFF); + } + } else if (isCapsLockOn() || mCapCharacters || mCapWords) { + setShiftState(LeanbackKeyboardView.SHIFT_ON); + } + } + + public void onStartInput(EditorInfo info) { + setImeOptions(mContext.getResources(), info); + mVoiceOn = false; + mLabel = LeanbackUtils.getEditorLabel(info); + } + + @SuppressLint("NewApi") + public void onStartInputView() { + clearSuggestions(); + LayoutParams params = (LayoutParams) mKeyboardsContainer.getLayoutParams(); + if (mSuggestionsEnabled) { + params.removeRule(RelativeLayout.ALIGN_PARENT_TOP); + mSuggestionsContainer.setVisibility(View.VISIBLE); + mSuggestionsBg.setVisibility(View.VISIBLE); + } else { + params.addRule(RelativeLayout.ALIGN_PARENT_TOP); + mSuggestionsContainer.setVisibility(View.GONE); + mSuggestionsBg.setVisibility(View.GONE); + } + + mKeyboardsContainer.setLayoutParams(params); + mMainKeyboardView.setKeyboard(mInitialMainKeyboard); + mVoiceButtonView.setMicEnabled(mVoiceEnabled); + resetVoice(); + dismissMiniKeyboard(); + if (!TextUtils.isEmpty(mEnterKeyText)) { + mActionButtonView.setText(mEnterKeyText); + mActionButtonView.setContentDescription(mEnterKeyText); + } else { + mActionButtonView.setText(mEnterKeyTextResId); + mActionButtonView.setContentDescription(mContext.getString(mEnterKeyTextResId)); + } + + if (mCapCharacters) { + setShiftState(LeanbackKeyboardView.SHIFT_LOCKED); + } else if (!mCapSentences && !mCapWords) { + setShiftState(LeanbackKeyboardView.SHIFT_OFF); + } else { + setShiftState(LeanbackKeyboardView.SHIFT_ON); + } + } + + public void onTextEntry() { + if (mMainKeyboardView.isShifted()) { + if (!isCapsLockOn() && !mCapCharacters) { + setShiftState(LeanbackKeyboardView.SHIFT_OFF); + } + } else if (isCapsLockOn() || mCapCharacters) { + setShiftState(LeanbackKeyboardView.SHIFT_LOCKED); + } + + if (dismissMiniKeyboard()) { + moveFocusToIndex(mMiniKbKeyIndex, KeyFocus.TYPE_MAIN); + } + + } + + public void onVoiceClick() { + if (mVoiceButtonView != null) { + mVoiceButtonView.onClick(); + } + + } + + public void resetFocusCursor() { + offsetRect(mRect, mMainKeyboardView); + mX = (float) ((double) mRect.left + (double) mRect.width() * 0.45D); + mY = (float) ((double) mRect.top + (double) mRect.height() * 0.375D); + getBestFocus(mX, mY, mTempKeyInfo); + setKbFocus(mTempKeyInfo, true, false); + setTouchStateInternal(0); + mSelectorAnimator.reverse(); + mSelectorAnimator.end(); + } + + public void resetVoice() { + mMainKeyboardView.setAlpha(mAlphaIn); + mActionButtonView.setAlpha(mAlphaIn); + mVoiceButtonView.setAlpha(mAlphaOut); + mMainKeyboardView.setVisibility(View.VISIBLE); + mActionButtonView.setVisibility(View.VISIBLE); + mVoiceButtonView.setVisibility(View.INVISIBLE); + } + + public void setDismissListener(DismissListener listener) { + mDismissListener = listener; + } + + public void setFocus(KeyFocus focus) { + setKbFocus(focus, false, true); + } + + public void setFocus(KeyFocus focus, final boolean animate) { + setKbFocus(focus, false, animate); + } + + /** + * NOTE: Draw selection over the focused key.
+ * Show selection animation when moving from one button to another. + */ + public void setSelectorToFocus(Rect rect, boolean overestimateWidth, boolean overestimateHeight, boolean animate) { + if (mSelector.getWidth() != 0 && 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 * mOverestimate; + } + + float widthOver = width; + if (overestimateWidth) { + widthOver = width * mOverestimate; + } + + float deltaY = heightOver; + float deltaX = widthOver; + float maxDelta = Math.max(widthOver, heightOver); + float minDelta = Math.min(widthOver, heightOver); + if ((double) (maxDelta / minDelta) < 1.1D) { + deltaY = maxDelta; + deltaX = maxDelta; + } + + final float x = rect.exactCenterX() - deltaX / 2.0F; + final float y = rect.exactCenterY() - deltaY / 2.0F; + mSelectorAnimation.cancel(); + + // Fix 9-patch stretching for square keys (especially on large keyboard). + if (Math.abs(deltaX - deltaY) < 1) { // is square + mKeySelector.setBackground(mKeySelectorSquare); + } else { + mKeySelector.setBackground(mKeySelectorStretched); + } + + if (animate) { + mSelectorAnimation.reset(); + mSelectorAnimation.setAnimationBounds(x, y, deltaX, deltaY); + mSelector.startAnimation(mSelectorAnimation); + } else { + mSelectorAnimation.setValues(x, y, deltaX, deltaY); + } + } + } + + /** + * Set touch state + * @param state state e.g. {@link LeanbackKeyboardContainer#TOUCH_STATE_CLICK LeanbackKeyboardContainer.TOUCH_STATE_CLICK} + */ + public void setTouchState(final int state) { + switch (state) { + case TOUCH_STATE_NO_TOUCH: + if (mTouchState == TOUCH_STATE_TOUCH_MOVE || mTouchState == TOUCH_STATE_CLICK) { + mSelectorAnimator.reverse(); + } + break; + case TOUCH_STATE_TOUCH_SNAP: + if (mTouchState == TOUCH_STATE_CLICK) { + mSelectorAnimator.reverse(); + } else if (mTouchState == TOUCH_STATE_TOUCH_MOVE) { + mSelectorAnimator.reverse(); + } + break; + case TOUCH_STATE_TOUCH_MOVE: + if (mTouchState == TOUCH_STATE_NO_TOUCH || mTouchState == TOUCH_STATE_TOUCH_SNAP) { + mSelectorAnimator.start(); + } + break; + case TOUCH_STATE_CLICK: + if (mTouchState == TOUCH_STATE_NO_TOUCH || mTouchState == TOUCH_STATE_TOUCH_SNAP) { + mSelectorAnimator.start(); + } + } + + setTouchStateInternal(state); + setKbFocus(mCurrKeyInfo, true, true); + } + + public void setVoiceListener(VoiceListener listener) { + mVoiceListener = listener; + } + + public void startVoiceRecording() { + if (mVoiceEnabled) { + if (!mVoiceKeyDismissesEnabled) { + mVoiceAnimator.startEnterAnimation(); + } else { + mDismissListener.onDismiss(true); + } + } + } + + /** + * Switch to next keyboard (looped). + * {@link KeyboardManager KeyboardManager} is the source behind all keyboard implementations + */ + public void switchToNextKeyboard() { + KeyboardData nextKeyboard = mKeyboardManager.next(); + Keyboard currentKeyboard = mMainKeyboardView.getKeyboard(); + + if (currentKeyboard != null && + currentKeyboard.equals(nextKeyboard.abcKeyboard)) { // one keyboard in the list + // Prompt user to select layout. + Helpers.startActivity(mContext, KbLayoutActivity.class); + mContext.hideIme(); + } else { + mInitialMainKeyboard = nextKeyboard.abcKeyboard; + mAbcKeyboard = nextKeyboard.abcKeyboard; + mMainKeyboardView.setKeyboard(nextKeyboard.abcKeyboard); + + mSymKeyboard = nextKeyboard.symKeyboard; + mNumKeyboard = nextKeyboard.numKeyboard; + } + } + + public void updateAddonKeyboard() { + mKeyboardManager.load(); // force reload to fix such errors as invisible kbd + KeyboardData keyboard = mKeyboardManager.get(); + mInitialMainKeyboard = keyboard.abcKeyboard; + mAbcKeyboard = keyboard.abcKeyboard; + mMainKeyboardView.setKeyboard(keyboard.abcKeyboard); + + mSymKeyboard = keyboard.symKeyboard; + mNumKeyboard = keyboard.numKeyboard; + + mThemeManager.updateKeyboardTheme(); + } + + public void updateSuggestions(ArrayList suggestions) { + addUserInputToSuggestions(suggestions); + + int oldCount = mSuggestions.getChildCount(); + int newCount = suggestions.size(); + if (newCount < oldCount) { + mSuggestions.removeViews(newCount, oldCount - newCount); + } else if (newCount > oldCount) { + while (oldCount < newCount) { + View suggestion = mContext.getLayoutInflater().inflate(R.layout.candidate, null); + mSuggestions.addView(suggestion); + ++oldCount; + } + } + + for (oldCount = 0; oldCount < newCount; ++oldCount) { + Button suggestion = mSuggestions.getChildAt(oldCount).findViewById(R.id.text); + suggestion.setText(suggestions.get(oldCount)); + suggestion.setContentDescription(suggestions.get(oldCount)); + } + + if (getCurrFocus().type == KeyFocus.TYPE_SUGGESTION) { + resetFocusCursor(); + } + + mThemeManager.updateSuggestionsTheme(); + } + + /** + * Useful for password fields + */ + private void addUserInputToSuggestions(ArrayList suggestions) { + InputConnection connection = mContext.getCurrentInputConnection(); + + if (connection != null) { + String editorText = LeanbackUtils.getEditorText(connection); + + if (editorText.isEmpty()) { + editorText = mLabel; + } + + if (suggestions.size() == 0) { + suggestions.add(editorText); + } else { + suggestions.set(0, editorText); + } + } + } + + public void onLangKeyClick() { + switchToNextKeyboard(); + } + + public void onClipboardClick(InputListener listener) { + ClipboardManager clipBoard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipBoard != null) { + ClipData clipData = clipBoard.getPrimaryClip(); + if (clipData != null) { + ClipData.Item item = clipData.getItemAt(0); + String text = item.getText().toString(); + if (listener != null) { + listener.onEntry(InputListener.ENTRY_TYPE_STRING, LeanbackKeyboardView.NOT_A_KEY, text); + } + } + } + } + + public interface DismissListener { + void onDismiss(boolean fromVoice); + } + + 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 = TYPE_INVALID; + + @Override + public boolean equals(Object obj) { + if (this != obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + KeyFocus focus = (KeyFocus) obj; + + if (this.code != focus.code) { + return false; + } + + if (this.index != focus.index) { + return false; + } + + if (this.type != focus.type) { + return false; + } + + // equality must be commutative + if (this.label == null && focus.label != null) { + return false; + } + + if (this.label != null && !this.label.equals(focus.label)) { + return false; + } + + if (!this.rect.equals(focus.rect)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hash = this.rect.hashCode(); + int index = this.index; + int type = this.type; + int code = this.code; + int salt; + if (this.label != null) { + salt = this.label.hashCode(); + } else { + salt = 0; + } + + return (((hash * 31 + index) * 31 + type) * 31 + code) * 31 + salt; + } + + public void set(KeyFocus focus) { + this.index = focus.index; + this.type = focus.type; + this.code = focus.code; + this.label = focus.label; + this.rect.set(focus.rect); + } + + @Override + public String toString() { + return "[type: " + this.type + ", index: " + this.index + ", code: " + this.code + ", label: " + this.label + ", rect: " + this.rect + "]"; + } + } + + private class ScaleAnimation extends Animation { + private float mEndHeight; + private float mEndWidth; + private float mEndX; + private float mEndY; + private final ViewGroup.LayoutParams mParams; + private float mStartHeight; + private float mStartWidth; + private float mStartX; + private float mStartY; + private final View mView; + + public ScaleAnimation(FrameLayout view) { + mView = view; + mParams = view.getLayoutParams(); + setDuration(MOVEMENT_ANIMATION_DURATION); + setInterpolator(sMovementInterpolator); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation transformation) { + if (interpolatedTime == 0.0F) { + mStartX = mView.getX(); + mStartY = mView.getY(); + mStartWidth = (float) mParams.width; + mStartHeight = (float) mParams.height; + } else { + setValues((mEndX - mStartX) * interpolatedTime + mStartX, + (mEndY - mStartY) * interpolatedTime + mStartY, + (float) ((int) ((mEndWidth - mStartWidth) * interpolatedTime + mStartWidth)), + (float) ((int) ((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight))); + } + } + + public void setAnimationBounds(float x, float y, float width, float height) { + mEndX = x; + mEndY = y; + mEndWidth = width; + mEndHeight = height; + } + + public void setValues(float x, float y, float width, float height) { + mView.setX(x); + mView.setY(y); + mParams.width = (int) width; + mParams.height = (int) height; + mView.setLayoutParams(mParams); + mView.requestLayout(); + } + } + + private class VoiceIntroAnimator { + private AnimatorListener mEnterListener; + private AnimatorListener mExitListener; + private ValueAnimator mValueAnimator; + + public VoiceIntroAnimator(AnimatorListener enterListener, AnimatorListener exitListener) { + mEnterListener = enterListener; + mExitListener = exitListener; + mValueAnimator = ValueAnimator.ofFloat(mAlphaOut, mAlphaIn); + mValueAnimator.setDuration(mVoiceAnimDur); + mValueAnimator.setInterpolator(new AccelerateInterpolator()); + } + + private void start(final boolean enterVoice) { + mValueAnimator.cancel(); + mValueAnimator.removeAllListeners(); + + AnimatorListener listener; + + if (enterVoice) { + listener = mEnterListener; + } else { + listener = mExitListener; + } + + mValueAnimator.addListener(listener); + mValueAnimator.removeAllUpdateListeners(); + mValueAnimator.addUpdateListener(animation -> { + float scale = (Float) mValueAnimator.getAnimatedValue(); + float calcOpacity = mAlphaIn + mAlphaOut - scale; + float opacity; + if (enterVoice) { + opacity = calcOpacity; + } else { + opacity = scale; + } + + if (enterVoice) { + calcOpacity = scale; + } + + mMainKeyboardView.setAlpha(opacity); + mActionButtonView.setAlpha(opacity); + mVoiceButtonView.setAlpha(calcOpacity); + if (scale == mAlphaOut) { + if (!enterVoice) { + mMainKeyboardView.setVisibility(View.VISIBLE); + mActionButtonView.setVisibility(View.VISIBLE); + return; + } + + mVoiceButtonView.setVisibility(View.VISIBLE); + } else if (scale == mAlphaIn) { + if (enterVoice) { + mMainKeyboardView.setVisibility(View.INVISIBLE); + mActionButtonView.setVisibility(View.INVISIBLE); + return; + } + + mVoiceButtonView.setVisibility(View.INVISIBLE); + } + }); + mValueAnimator.start(); + } + + void startEnterAnimation() { + if (!isVoiceVisible() && !mValueAnimator.isRunning()) { + start(true); + } + } + + void startExitAnimation() { + if (isVoiceVisible() && !mValueAnimator.isRunning()) { + start(false); + } + } + } + + public interface VoiceListener { + void onVoiceResult(String result); + } + + private class MyVoiceRecognitionListener implements RecognitionListener { + float peakRmsLevel = 0.0F; + int rmsCounter = 0; + + @Override + public void onBeginningOfSpeech() { + mVoiceButtonView.showRecording(); + } + + @Override + public void onBufferReceived(byte[] buffer) { + // NOP + } + + @Override + public void onEndOfSpeech() { + mVoiceButtonView.showRecognizing(); + mVoiceOn = false; + } + + @Override + public void onError(int error) { + cancelVoiceRecording(); + + String errorMsg; + + switch (error) { + case SpeechRecognizer.ERROR_SERVER: + errorMsg = "recognizer error server error"; + break; + case SpeechRecognizer.ERROR_CLIENT: + errorMsg = "recognizer error client error"; + break; + case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: + errorMsg = "recognizer error speech timeout"; + break; + case SpeechRecognizer.ERROR_NO_MATCH: + errorMsg = "recognizer error no match"; + break; + default: + errorMsg = "recognizer other error " + error; + } + + MessageHelpers.showLongMessage(mContext, errorMsg); + + Log.d(TAG, errorMsg); + } + + @Override + public void onEvent(int eventType, Bundle bundle) { + // NOP + } + + @Override + public void onPartialResults(Bundle bundle) { + // NOP + } + + @Override + public void onReadyForSpeech(Bundle bundle) { + mVoiceButtonView.showListening(); + } + + @Override + public void onResults(Bundle bundle) { + List results = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); + + if (results != null && mVoiceListener != null) { + mVoiceListener.onVoiceResult(results.get(0)); + } + + cancelVoiceRecording(); + } + + // TODO: not fully decompiled and may contains bugs + @Override + public void onRmsChanged(float rmsdB) { + synchronized (this) { + mVoiceOn = true; + + int speechLevel = 0; + + if (rmsdB >= 0) { + speechLevel = (int) (rmsdB * 10f); + } + + mSpeechLevelSource.setSpeechLevel(speechLevel); + + peakRmsLevel = Math.max(peakRmsLevel, rmsdB); + rmsCounter++; + + if (rmsCounter <= 100) { + return; + } + + if (peakRmsLevel < 0) { + return; + } + + mVoiceButtonView.showNotListening(); + } + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardController.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardController.java new file mode 100644 index 0000000..2606039 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardController.java @@ -0,0 +1,955 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.graphics.PointF; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard.Key; +import android.os.Handler; +import android.text.InputType; +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 androidx.annotation.NonNull; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer.KeyFocus; +import com.liskovsoft.leankeyboard.ime.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"; + public static final String TAG_GO = "Go"; + private boolean mClickConsumed; + private long mLastClickTime; + private LeanbackKeyboardContainer mContainer; + private InputMethodService mContext; + private DoubleClickDetector mDoubleClickDetector; + private LeanbackKeyboardContainer.KeyFocus mCurrentFocus; + private Handler mHandler; + private 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 mPrevTime; + private boolean mShowInput; + private int mLastEditorIdPhysicalKeyboardWasUsed; + private boolean mHideKeyboardWhenPhysicalKeyboardUsed = true; + + public LeanbackKeyboardController(final InputMethodService context, + final InputListener listener) { + this(context, listener, new TouchNavSpaceTracker(), new LeanbackKeyboardContainer(context)); + } + + public LeanbackKeyboardController(final InputMethodService context, + final InputListener listener, + final TouchNavSpaceTracker tracker, + final LeanbackKeyboardContainer container) { + mDoubleClickDetector = new DoubleClickDetector(); + mOnLayoutChangeListener = (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + left = right - left; + top = bottom - top; + if (left > 0 && top > 0) { + if (mSpaceTracker != null) { + mSpaceTracker.setPixelSize((float) left, (float) top); + } + + if (left != oldRight - oldLeft || top != oldBottom - oldTop) { + initInputView(); + } + } + + }; + mTouchEventListener = new TouchEventListener(); + mCurrentFocus = new KeyFocus(); + mTempFocus = new KeyFocus(); + mKeyChangeHistory = new ArrayList<>(11); + mTempPoint = new PointF(); + mKeyDownReceived = false; + mLongPressHandled = false; + mContext = context; + mResizeSquareDistance = context.getResources().getDimension(R.dimen.resize_move_distance); + mResizeSquareDistance *= mResizeSquareDistance; + mInputListener = listener; + setSpaceTracker(tracker); + setKeyboardContainer(container); + mContainer.setVoiceListener(this); + mContainer.setDismissListener(this); + } + + private boolean applyLETVFixesDown(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + return true; + default: + return false; + } + } + + private boolean applyLETVFixesUp(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + mContainer.switchToNextKeyboard(); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + fakeKeyIndex(0, KeyFocus.TYPE_ACTION); + break; + case KeyEvent.KEYCODE_MEDIA_REWIND: + fakeKeyCode(LeanbackKeyboardView.KEYCODE_DELETE); + break; + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + fakeKeyCode(LeanbackKeyboardView.ASCII_SPACE); + break; + default: + return false; + } + + return true; + } + + private void beginLongClickCountdown() { + this.mClickConsumed = false; + Handler handler = this.mHandler; + if (handler == null) { + handler = new Handler(); + this.mHandler = handler; + } + + handler.removeCallbacks(this); + handler.postDelayed(this, (long) 1000); + } + + private void clearKeyIfNecessary() { + ++mMoveCount; + if (mMoveCount >= 3) { + mMoveCount = 0; + mKeyDownKeyFocus = null; + } + + } + + private void commitKey() { + this.commitKey(this.mContainer.getCurrFocus()); + } + + /** + * NOTE: Where all magic happens. Input from virtual kbd is processed here. + * @param focus current key + */ + private void commitKey(KeyFocus focus) { + if (mContainer != null && focus != null) { + switch (focus.type) { + case KeyFocus.TYPE_VOICE: + mContainer.onVoiceClick(); + return; + case KeyFocus.TYPE_ACTION: // User presses Go, Send, Search etc + mInputListener.onEntry(InputListener.ENTRY_TYPE_ACTION, 0, null); + // mContext.hideWindow(); // SmartYouTubeTV fix: force hide keyboard + return; + case KeyFocus.TYPE_SUGGESTION: + mInputListener.onEntry(InputListener.ENTRY_TYPE_SUGGESTION, 0, mContainer.getSuggestionText(focus.index)); + return; + default: + Key key = mContainer.getKey(focus.type, focus.index); + if (key != null) { + handleCommitKeyboardKey(key.codes[0], key.label); + } + } + } + } + + private void fakeClickDown() { + mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK); + } + + private void fakeClickUp() { + LeanbackKeyboardContainer container = mContainer; + commitKey(container.getCurrFocus()); + container.setTouchState(0); + } + + private void fakeKeyCode(final int keyCode) { + mContainer.getCurrFocus().code = keyCode; + handleCommitKeyboardKey(keyCode, null); + } + + /** + * Fake key index + * @param index key index + * @param type {@link KeyFocus KeyFocus} constant + */ + private void fakeKeyIndex(final int index, final int type) { + LeanbackKeyboardContainer.KeyFocus focus = mContainer.getCurrFocus(); + focus.index = index; + focus.type = type; + commitKey(focus); + } + + private void fakeLongClickDown() { + LeanbackKeyboardContainer container = mContainer; + container.onKeyLongPress(); + container.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK); + } + + private void fakeLongClickUp() { + this.mContainer.setTouchState(0); + } + + private PointF getBestSnapPosition(final PointF currPoint, final long currTime) { + if (mKeyChangeHistory.size() <= 1) { + return currPoint; + } else { + int count = 0; + + PointF pos; + while (true) { + pos = currPoint; + if (count >= mKeyChangeHistory.size() - 1) { + break; + } + + LeanbackKeyboardController.KeyChange change = mKeyChangeHistory.get(count); + if (currTime - mKeyChangeHistory.get(count + 1).time < KEY_CHANGE_REVERT_TIME_MS) { + pos = change.position; + mKeyChangeHistory.clear(); + mKeyChangeHistory.add(new LeanbackKeyboardController.KeyChange(currTime, pos)); + break; + } + + ++count; + } + + return pos; + } + } + + private PointF getCurrentKeyPosition() { + if (mContainer != null) { + LeanbackKeyboardContainer.KeyFocus focus = mContainer.getCurrFocus(); + return new PointF((float) focus.rect.centerX(), (float) focus.rect.centerY()); + } else { + return null; + } + } + + private PointF getRelativePosition(View view, MotionEvent event) { + int[] location = new int[2]; + view.getLocationOnScreen(location); + float x = event.getRawX(); + float y = event.getRawY(); + return new PointF(x - (float) location[0], y - (float) location[1]); + } + + private int getSimplifiedKey(final int keyCode) { + int defaultCode = KeyEvent.KEYCODE_DPAD_CENTER; + if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER) { + final byte enter = KeyEvent.KEYCODE_ENTER; + defaultCode = enter; + if (keyCode != KeyEvent.KEYCODE_ENTER) { + defaultCode = enter; + if (keyCode != KeyEvent.KEYCODE_NUMPAD_ENTER) { + defaultCode = keyCode; + if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { + defaultCode = enter; + } + } + } + } + + if (defaultCode == KeyEvent.KEYCODE_BUTTON_B) { + defaultCode = KeyEvent.KEYCODE_BACK; + } + + return defaultCode; + } + + /** + * NOTE: Specials keys (e.g. voice key) handled here + * @param keyCode key code e.g {@link LeanbackKeyboardView#KEYCODE_SHIFT LeanbackKeyboardView.KEYCODE_SHIFT} + * @param text typed content + */ + private void handleCommitKeyboardKey(int keyCode, CharSequence text) { + switch (keyCode) { + case LeanbackKeyboardView.KEYCODE_DISMISS_MINI_KEYBOARD: + mContainer.dismissMiniKeyboard(); + return; + case LeanbackKeyboardView.KEYCODE_VOICE: + mContainer.startVoiceRecording(); + return; + case LeanbackKeyboardView.KEYCODE_CAPS_LOCK: + mContainer.onShiftDoubleClick(mContainer.isCapsLockOn()); + return; + case LeanbackKeyboardView.KEYCODE_DELETE: + mInputListener.onEntry(InputListener.ENTRY_TYPE_BACKSPACE, LeanbackKeyboardView.SHIFT_OFF, null); + return; + case LeanbackKeyboardView.KEYCODE_RIGHT: + mInputListener.onEntry(InputListener.ENTRY_TYPE_RIGHT, LeanbackKeyboardView.SHIFT_OFF, null); + return; + case LeanbackKeyboardView.KEYCODE_LEFT: + mInputListener.onEntry(InputListener.ENTRY_TYPE_LEFT, LeanbackKeyboardView.SHIFT_OFF, null); + return; + case LeanbackKeyboardView.KEYCODE_SYM_TOGGLE: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "mode change"); + } + + mContainer.onModeChangeClick(); + return; + case LeanbackKeyboardView.KEYCODE_SHIFT: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "shift"); + } + + mContainer.onShiftClick(); + return; + case LeanbackKeyboardView.ASCII_SPACE: + mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, keyCode, " "); + mContainer.onSpaceEntry(); + return; + case LeanbackKeyboardView.ASCII_PERIOD: + mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, keyCode, text); + mContainer.onPeriodEntry(); + return; + case LeanbackKeyboardView.KEYCODE_LANG_TOGGLE: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "language change"); + } + + mContainer.onLangKeyClick(); + return; + case LeanbackKeyboardView.KEYCODE_CLIPBOARD: + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "paste from clipboard"); + } + + mContainer.onClipboardClick(mInputListener); + return; + default: + mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, keyCode, text); + mContainer.onTextEntry(); + if (mContainer.isMiniKeyboardOnScreen()) { + mContainer.dismissMiniKeyboard(); + } + + } + } + + private boolean handleKeyDownEvent(int keyCode, int eventRepeatCount) { + keyCode = getSimplifiedKey(keyCode); + boolean handled; + if (keyCode == KeyEvent.KEYCODE_BACK) { + mContainer.cancelVoiceRecording(); + handled = false; + } else if (mContainer.isVoiceVisible()) { + if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { + mContainer.cancelVoiceRecording(); + } + + handled = true; + } else { + handled = true; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_UP); + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_DOWN); + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_LEFT); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_RIGHT); + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (eventRepeatCount == 0) { + mMoveCount = 0; + mKeyDownKeyFocus = new LeanbackKeyboardContainer.KeyFocus(); + mKeyDownKeyFocus.set(mContainer.getCurrFocus()); + } else if (eventRepeatCount == 1 && handleKeyLongPress(keyCode)) { // space long press handler and others + mKeyDownKeyFocus = null; + } + + handled = true; + if (isKeyHandledOnKeyDown(mContainer.getCurrKeyCode())) { + commitKey(); + handled = true; + } + break; + case KeyEvent.KEYCODE_BUTTON_X: + handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_DELETE, null); + handled = true; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + handleCommitKeyboardKey(LeanbackKeyboardView.ASCII_SPACE, null); + handled = true; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_LEFT, null); + handled = true; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_RIGHT, null); + handled = true; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + case KeyEvent.KEYCODE_BUTTON_THUMBR: + break; + default: + handled = false; + } + } + + if (!handled) { + handled = applyLETVFixesDown(keyCode); + } + + return handled; + } + + private boolean handleKeyLongPress(int keyCode) { + mLongPressHandled = isEnterKey(keyCode) && mContainer.onKeyLongPress(); + + if (mContainer.isMiniKeyboardOnScreen()) { + Log.d(TAG, "mini keyboard shown after long press"); + } + + return mLongPressHandled; + } + + private boolean handleKeyUpEvent(int keyCode, long currTime) { + keyCode = getSimplifiedKey(keyCode); + boolean handled; + + if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { + handled = false; + } else if (mContainer.isVoiceVisible()) { + handled = true; + } else { + handled = true; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + clearKeyIfNecessary(); + handled = true; + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (mContainer.getCurrKeyCode() == LeanbackKeyboardView.KEYCODE_SHIFT) { + mDoubleClickDetector.addEvent(currTime); + handled = true; + } else { + handled = true; + if (!isKeyHandledOnKeyDown(mContainer.getCurrKeyCode())) { + commitKey(mKeyDownKeyFocus); + handled = true; + } + } + case KeyEvent.KEYCODE_BUTTON_X: + case KeyEvent.KEYCODE_BUTTON_Y: + case KeyEvent.KEYCODE_BUTTON_L1: + case KeyEvent.KEYCODE_BUTTON_R1: + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_SYM_TOGGLE, null); + handled = true; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_CAPS_LOCK, null); + handled = true; + break; + case KeyEvent.KEYCODE_VOICE_ASSIST: + case KeyEvent.KEYCODE_SEARCH: + mContainer.startVoiceRecording(); + handled = true; + break; + default: + handled = false; + } + } + + if (!handled) { + handled = applyLETVFixesUp(keyCode); + } + + return handled; + } + + private void initInputView() { + mContainer.onInitInputView(); + updatePositionToCurrentFocus(); + } + + /** + * Simple throttle routine. + * @param callInterval interval + * @return is allowed + */ + private boolean isCallAllowedOrigin(int callInterval) { + long currTimeMS = System.currentTimeMillis(); + long timeDelta = currTimeMS - mPrevTime; + if (mPrevTime != 0 && timeDelta <= (callInterval * 3)) { + if (timeDelta > callInterval) { + mPrevTime = 0; + return true; + } + } else { + mPrevTime = currTimeMS; + } + + return false; + } + + /** + * Simple throttle routine. Simplified comparing to previous. Not tested yet!!!! + * @param interval interval + * @return is allowed + */ + private boolean isCallAllowed2(int interval) { + long currTimeMS = System.currentTimeMillis(); + long timeDelta = currTimeMS - mPrevTime; + if (mPrevTime == 0) { + mPrevTime = currTimeMS; + return true; + } else if (timeDelta > interval) { + mPrevTime = 0; + } + + return false; + } + + private boolean isDoubleClick() { + long currTimeMS = System.currentTimeMillis(); + long lastTime = mLastClickTime; + if (mLastClickTime != 0L && currTimeMS - lastTime <= (long) 300) { + return true; + } else { + mLastClickTime = currTimeMS; + return false; + } + } + + private boolean isEnterKey(int keyCode) { + keyCode = getSimplifiedKey(keyCode); + return keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER; + } + + /** + * Whether key down is handled + * @param currKeyCode key code e.g. {@link LeanbackKeyboardView#KEYCODE_DELETE LeanbackKeyboardView.KEYCODE_DELETE} + * @return key down is handled + */ + private boolean isKeyHandledOnKeyDown(int currKeyCode) { + return currKeyCode == LeanbackKeyboardView.KEYCODE_DELETE || currKeyCode == LeanbackKeyboardView.KEYCODE_LEFT || currKeyCode == LeanbackKeyboardView.KEYCODE_RIGHT; + } + + private void moveSelectorToPoint(float x, float y) { + LeanbackKeyboardContainer container = mContainer; + LeanbackKeyboardContainer.KeyFocus focus = mTempFocus; + container.getBestFocus(x, y, focus); + mContainer.setFocus(mTempFocus, false); + } + + private boolean onDirectionalMove(int dir) { + if (mContainer.getNextFocusInDirection(dir, mCurrentFocus, mTempFocus)) { + mContainer.updateCyclicFocus(dir, mCurrentFocus, mTempFocus); + mContainer.setFocus(mTempFocus); + mCurrentFocus.set(mTempFocus); + clearKeyIfNecessary(); + } + + return true; + } + + private void performBestSnap(long time) { + LeanbackKeyboardContainer.KeyFocus focus = mContainer.getCurrFocus(); + mTempPoint.x = (float) focus.rect.centerX(); + mTempPoint.y = (float) focus.rect.centerY(); + PointF pos = getBestSnapPosition(mTempPoint, time); + mContainer.getBestFocus(pos.x, pos.y, mTempFocus); + mContainer.setFocus(mTempFocus); + updatePositionToCurrentFocus(); + } + + /** + * Set key state + * @param keyIndex key index + * @param keyState constant e.g. {@link LeanbackKeyboardContainer#TOUCH_STATE_CLICK LeanbackKeyboardContainer.TOUCH_STATE_CLICK} + */ + private void setKeyState(int keyIndex, boolean keyState) { + LeanbackKeyboardContainer container = this.mContainer; + LeanbackKeyboardContainer.KeyFocus focus = container.getCurrFocus(); + focus.index = keyIndex; + focus.type = KeyFocus.TYPE_MAIN; + byte state; + if (keyState) { + state = LeanbackKeyboardContainer.TOUCH_STATE_CLICK; + } else { + state = LeanbackKeyboardContainer.TOUCH_STATE_NO_TOUCH; + } + + container.setTouchState(state); + } + + private void updatePositionToCurrentFocus() { + PointF pos = getCurrentKeyPosition(); + if (pos != null && mSpaceTracker != null) { + mSpaceTracker.setPixelPosition(pos.x, pos.y); + } + + } + + public boolean areSuggestionsEnabled() { + return mContainer != null && mContainer.areSuggestionsEnabled(); + } + + public void setSuggestionsEnabled(boolean enabled) { + if (mContainer != null) { + mContainer.setSuggestionsEnabled(enabled); + } + } + + public boolean enableAutoEnterSpace() { + return mContainer != null && mContainer.enableAutoEnterSpace(); + } + + public View getView() { + if (mContainer != null) { + RelativeLayout view = mContainer.getView(); + view.setClickable(true); + view.setOnTouchListener(this); + view.setOnHoverListener(this); + Button button = mContainer.getGoButton(); + button.setOnTouchListener(this); + button.setOnHoverListener(this); + button.setTag(TAG_GO); + return view; + } else { + return null; + } + } + + public void onDismiss(boolean fromVoice) { + if (fromVoice) { + mInputListener.onEntry(InputListener.ENTRY_TYPE_VOICE_DISMISS, LeanbackKeyboardView.SHIFT_OFF, null); + } else { + mInputListener.onEntry(InputListener.ENTRY_TYPE_DISMISS, LeanbackKeyboardView.SHIFT_OFF, null); + } + } + + public boolean onGenericMotionEvent(MotionEvent event) { + return mSpaceTracker != null && mContext != null && mContext.isInputViewShown() && mSpaceTracker.onGenericMotionEvent(event); + } + + /** + * Control keyboard from the mouse. Movement catching + * @param view active view + * @param event event object + * @return is hover handled + */ + @Override + public boolean onHover(View view, MotionEvent event) { + boolean handled = false; + if (event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { + PointF pos = getRelativePosition(mContainer.getView(), event); + moveSelectorToPoint(pos.x, pos.y); + handled = true; + } + + return handled; + } + + /** + * Try to handle key down event + * @param keyCode key code e.g. {@link KeyEvent#KEYCODE_ENTER KeyEvent.KEYCODE_ENTER} + * @param event {@link KeyEvent KeyEvent} + * @return is event handled + */ + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + //greater than zero means it is a physical keyboard. + //we also want to hide the view if it's a glyph (for example, not physical volume-up key) + //if (event.getDeviceId() > 0 && event.isPrintingKey()) onPhysicalKeyboardKeyPressed(); + if (event.isPrintingKey()) onPhysicalKeyboardKeyPressed(); + + mCurrentFocus.set(mContainer.getCurrFocus()); + if (mSpaceTracker != null && mSpaceTracker.onKeyDown(keyCode, event)) { + return true; + } else { + if (isEnterKey(keyCode)) { + mKeyDownReceived = true; + if (event.getRepeatCount() == 0) { + mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK); + } + } + + return handleKeyDownEvent(keyCode, event.getRepeatCount()); + } + } + + private void onPhysicalKeyboardKeyPressed() { + EditorInfo editorInfo = mContext.getCurrentInputEditorInfo(); + mLastEditorIdPhysicalKeyboardWasUsed = editorInfo == null ? 0 : editorInfo.fieldId; + if (mHideKeyboardWhenPhysicalKeyboardUsed) { + mContext.hideWindow(); + } + + // For all other keys, if we want to do transformations on + // text being entered with a hard keyboard, we need to process + // it and do the appropriate action. + // using physical keyboard is more annoying with candidate view in + // the way + // so we disable it. + + // stopping any soft-keyboard prediction + //abortCorrectionAndResetPredictionState(false); + } + + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { + if (mSpaceTracker != null && mSpaceTracker.onKeyUp(keyCode, keyEvent)) { + return true; + } else { + if (isEnterKey(keyCode)) { + if (!mKeyDownReceived || mLongPressHandled) { + mLongPressHandled = false; + return true; + } + + mKeyDownReceived = false; + if (mContainer.getTouchState() == 3) { + mContainer.setTouchState(1); + } + } + + return handleKeyUpEvent(keyCode, keyEvent.getEventTime()); + } + } + + public void onStartInput(EditorInfo info) { + if (mContainer != null) { + mContainer.onStartInput(info); + initInputView(); + } + + //// prevent accidental kbd pop-up on FireTV devices + //// more info: https://forum.xda-developers.com/fire-tv/general/guide-change-screen-keyboard-to-leankey-t3527675/page2 + //int maskAction = info.imeOptions & EditorInfo.IME_MASK_ACTION; + //mShowInput = maskAction != 0; + + mShowInput = info.inputType != InputType.TYPE_NULL; + } + + public boolean showInputView() { + return mShowInput; + } + + private void onHideIme() { + mContext.requestHideSelf(InputMethodService.BACK_DISPOSITION_DEFAULT); + } + + public void onStartInputView() { + mKeyDownReceived = false; + + if (mContainer != null) { + mContainer.onStartInputView(); + } + + mDoubleClickDetector.reset(); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + Object tag = view.getTag(); + boolean isEnterKey = TAG_GO.equals(tag); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (isEnterKey) { + break; + } + + moveSelectorToPoint(event.getX(), event.getY()); + fakeClickDown(); + beginLongClickCountdown(); + break; + case MotionEvent.ACTION_UP: + if (isEnterKey) { + fakeKeyIndex(0, KeyFocus.TYPE_ACTION); + break; + } + + if (!mClickConsumed) { + mClickConsumed = true; + if (isDoubleClick()) { + mContainer.onKeyLongPress(); + break; + } + + fakeClickUp(); + } + + fakeLongClickUp(); + break; + default: + return false; + } + + return true; + } + + @Override + public void onVoiceResult(String result) { + mInputListener.onEntry(InputListener.ENTRY_TYPE_VOICE, 0, result); + } + + @Override + public void run() { + if (!mClickConsumed) { + mClickConsumed = true; + fakeLongClickDown(); + } + } + + public void setKeyboardContainer(LeanbackKeyboardContainer container) { + mContainer = container; + container.getView().addOnLayoutChangeListener(mOnLayoutChangeListener); + } + + public void setSpaceTracker(TouchNavSpaceTracker tracker) { + mSpaceTracker = tracker; + tracker.setLPFEnabled(true); + tracker.setKeyEventListener(mTouchEventListener); + } + + public void initKeyboards() { + mContainer.initKeyboards(); + } + + public void updateSuggestions(ArrayList suggestions) { + if (mContainer != null) { + mContainer.updateSuggestions(suggestions); + } + + } + + public void setHideWhenPhysicalKeyboardUsed(boolean hide) { + mHideKeyboardWhenPhysicalKeyboardUsed = hide; + } + + private class DoubleClickDetector { + final long DOUBLE_CLICK_TIMEOUT_MS; + boolean mFirstClickShiftLocked; + long mFirstClickTime; + + private DoubleClickDetector() { + DOUBLE_CLICK_TIMEOUT_MS = 200L; + mFirstClickTime = 0L; + } + + public void addEvent(long currTime) { + if (currTime - mFirstClickTime > DOUBLE_CLICK_TIMEOUT_MS) { + mFirstClickTime = currTime; + mFirstClickShiftLocked = mContainer.isCapsLockOn(); + commitKey(); + } else { + mContainer.onShiftDoubleClick(mFirstClickShiftLocked); + reset(); + } + } + + public void reset() { + 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; + + /** + * User has typed something + * @param type type e.g. {@link InputListener#ENTRY_TYPE_ACTION InputListener.ENTRY_TYPE_ACTION} + * @param keyCode key code e.g. {@link LeanbackKeyboardView#SHIFT_ON LeanbackKeyboardView.SHIFT_ON} + * @param text text + */ + void onEntry(int type, int keyCode, CharSequence text); + } + + private static final class KeyChange { + public PointF position; + public long time; + + public KeyChange(long time, PointF position) { + this.time = time; + this.position = position; + } + } + + private class TouchEventListener implements TouchNavSpaceTracker.KeyEventListener { + private TouchEventListener() { + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (isEnterKey(keyCode)) { + mKeyDownReceived = true; + if (event.getRepeatCount() == 0) { + mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK); + mSpaceTracker.blockMovementUntil(event.getEventTime() + CLICK_MOVEMENT_BLOCK_DURATION_MS); + performBestSnap(event.getEventTime()); + } + } + + return handleKeyDownEvent(keyCode, event.getRepeatCount()); + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return handleKeyLongPress(keyCode); + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (isEnterKey(keyCode)) { + if (!mKeyDownReceived || mLongPressHandled) { + mLongPressHandled = false; + return true; + } + + mKeyDownReceived = false; + if (mContainer.getTouchState() == LeanbackKeyboardContainer.TOUCH_STATE_CLICK) { + mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_TOUCH_SNAP); + mSpaceTracker.unblockMovement(); + } + } + + return handleKeyUpEvent(keyCode, event.getEventTime()); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardView.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardView.java new file mode 100644 index 0000000..2c7fe9b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackKeyboardView.java @@ -0,0 +1,639 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import androidx.core.content.ContextCompat; +import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.Iterator; +import java.util.List; + +public class LeanbackKeyboardView extends FrameLayout { + private static final String TAG = "LbKbView"; + /** + * Space key index (important: wrong value will broke navigation) + */ + public static final int ASCII_PERIOD = 47; + /** + * Keys count among which space key spans (important: wrong value will broke navigation) + */ + public static final int ASCII_PERIOD_LEN = 5; + 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; + public static final int KEYCODE_LANG_TOGGLE = -9; + public static final int KEYCODE_CLIPBOARD = -10; + public 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 int mBaseMiniKbIndex = -1; + private final int mClickAnimDur; + private final float mClickedScale; + private final float mSquareIconScaleFactor; + 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 Keyboard mKeyboard; + private KeyHolder[] mKeys; + private boolean mMiniKeyboardOnScreen; + private Rect mPadding; + private int mRowCount; + private int mShiftState; + private final int mUnfocusStartDelay; + private final KeyConverter mConverter; + protected Paint mPaint; + protected int mKeyTextSize; + protected int mModeChangeTextSize; + private Drawable mCustomCapsLockDrawable; + + private static class KeyConverter { + private static final int LOWER_CASE = 0; + private static final int UPPER_CASE = 1; + + private void init(KeyHolder keyHolder) { + // store original label + // in case when two characters are stored in one label (e.g. "A|B") + if (keyHolder.key.text == null) { + keyHolder.key.text = keyHolder.key.label; + } + } + + public void toLowerCase(KeyHolder keyHolder) { + extractChar(LOWER_CASE, keyHolder); + } + + public void toUpperCase(KeyHolder keyHolder) { + extractChar(UPPER_CASE, keyHolder); + } + + private void extractChar(int charCase, KeyHolder keyHolder) { + init(keyHolder); + + CharSequence result = null; + CharSequence label = keyHolder.key.text; + + String[] labels = splitLabels(label); + + switch (charCase) { + case LOWER_CASE: + result = labels != null ? labels[0] : label.toString().toLowerCase(); + break; + case UPPER_CASE: + result = labels != null ? labels[1] : label.toString().toUpperCase(); + break; + } + + keyHolder.key.label = result; + } + + private String[] splitLabels(CharSequence label) { + String realLabel = label.toString(); + + String[] labels = realLabel.split("\\|"); + + return labels.length == 2 ? labels : null; // remember, we encoding two chars + } + } + + public LeanbackKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + Resources res = context.getResources(); + TypedArray styledAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LeanbackKeyboardView, 0, 0); + mRowCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_rowCount, -1); + mColCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_columnCount, -1); + mKeyTextSize = (int) res.getDimension(R.dimen.key_font_size); + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTextSize(mKeyTextSize); + mPaint.setTextAlign(Align.CENTER); + mPaint.setAlpha(255); + mPadding = new Rect(0, 0, 0, 0); + mModeChangeTextSize = (int) res.getDimension(R.dimen.function_key_mode_change_font_size); + mKeyTextColor = ContextCompat.getColor(getContext(), R.color.key_text_default); + mFocusIndex = -1; + mShiftState = 0; + mFocusedScale = res.getFraction(R.fraction.focused_scale, 1, 1); + mClickedScale = res.getFraction(R.fraction.clicked_scale, 1, 1); + mSquareIconScaleFactor = res.getFraction(R.fraction.square_icon_scale_factor, 1, 1); + mClickAnimDur = res.getInteger(R.integer.clicked_anim_duration); + mUnfocusStartDelay = res.getInteger(R.integer.unfocused_anim_delay); + mInactiveMiniKbAlpha = res.getInteger(R.integer.inactive_mini_kb_alpha); + mConverter = new KeyConverter(); + } + + private void adjustCase(KeyHolder keyHolder) { + boolean flag = keyHolder.isInMiniKb && keyHolder.isInvertible; + + // ^ equals to != + if (mKeyboard.isShifted() ^ flag) { + mConverter.toUpperCase(keyHolder); + } else { + mConverter.toLowerCase(keyHolder); + } + } + + /** + * NOTE: Adds key views to root window + */ + @SuppressLint("NewApi") + private ImageView createKeyImageView(final int keyIndex) { + Rect padding = mPadding; + int kbdPaddingLeft = getPaddingLeft(); + int kbdPaddingTop = getPaddingTop(); + KeyHolder keyHolder = mKeys[keyIndex]; + Key key = keyHolder.key; + adjustCase(keyHolder); + String label; + if (key.label == null) { + label = null; + } else { + label = key.label.toString(); + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "LABEL: " + key.label + "->" + label); + } + + Bitmap bitmap = Bitmap.createBitmap(key.width, key.height, Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Paint paint = mPaint; + paint.setColor(mKeyTextColor); + canvas.drawARGB(0, 0, 0, 0); + if (key.icon != null) { + if (key.codes[0] == NOT_A_KEY) { + switch (mShiftState) { + case SHIFT_OFF: + key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_off); + break; + case SHIFT_ON: + key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_on); + break; + case SHIFT_LOCKED: + if (mCustomCapsLockDrawable != null) { + key.icon = mCustomCapsLockDrawable; + } else { + key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_lock_on); + } + } + } + + // NOTE: Fix non proper scale of space key on low dpi + + int iconWidth = key.width; // originally used key.icon.getIntrinsicWidth(); + int iconHeight = key.height; // originally used key.icon.getIntrinsicHeight(); + + if (key.width == key.height) { // square key proper fit + int newSize = Math.round(key.width * mSquareIconScaleFactor); + iconWidth = newSize; + iconHeight = newSize; + } + + if (key.codes[0] == ASCII_SPACE && LeanKeyPreferences.instance(getContext()).getEnlargeKeyboard()) { + // space fix for large interface + float gap = getResources().getDimension(R.dimen.keyboard_horizontal_gap); + float gapDelta = (gap * 1.3f) - gap; + iconWidth -= gapDelta * (ASCII_PERIOD_LEN - 1); + } + + int dx = (key.width - padding.left - padding.right - iconWidth) / 2 + padding.left; + int dy = (key.height - padding.top - padding.bottom - iconHeight) / 2 + padding.top; + + canvas.translate((float) dx, (float) dy); + key.icon.setBounds(0, 0, iconWidth, iconHeight); + key.icon.draw(canvas); + canvas.translate((float) (-dx), (float) (-dy)); + } else if (label != null) { + if (label.length() > 1) { + paint.setTextSize((float) mModeChangeTextSize); + paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); + } else { + paint.setTextSize((float) mKeyTextSize); + paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + } + + canvas.drawText( + label, + (float) ((key.width - padding.left - padding.right) / 2 + padding.left), + (float) ((key.height - padding.top - padding.bottom) / 2) + (paint.getTextSize() - paint.descent()) / 2.0F + (float) padding.top, + paint + ); + paint.setShadowLayer(0.0F, 0.0F, 0.0F, 0); + } + + ImageView image = new ImageView(getContext()); + image.setImageBitmap(bitmap); + image.setContentDescription(label); + // Adds key views to root window + addView(image, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + // Set position manually for each key + image.setX((float) (key.x + kbdPaddingLeft)); + image.setY((float) (key.y + kbdPaddingTop)); + int opacity; + if (mMiniKeyboardOnScreen && !keyHolder.isInMiniKb) { + opacity = mInactiveMiniKbAlpha; + } else { + opacity = 255; + } + + image.setImageAlpha(opacity); + image.setVisibility(View.VISIBLE); + + return image; + } + + private void createKeyImageViews(KeyHolder[] keys) { + if (mKeyImageViews != null) { + ImageView[] images = mKeyImageViews; + int totalImages = images.length; + + for (int i = 0; i < totalImages; ++i) { + removeView(images[i]); + } + + mKeyImageViews = null; + } + + int totalKeys = keys.length; + for (int i = 0; i < totalKeys; ++i) { + if (mKeyImageViews == null) { + mKeyImageViews = new ImageView[totalKeys]; + } else if (mKeyImageViews[i] != null) { + removeView(mKeyImageViews[i]); + } + + mKeyImageViews[i] = createKeyImageView(i); + } + + } + + private void removeMessages() { + // TODO: not implemented + Log.w(TAG, "method 'removeMessages()' not implemented"); + } + + /** + * NOTE: Keys initialization routine.
+ * Any manipulations with keys should be done here. + */ + private void setKeys(List keys) { + mKeys = new KeyHolder[keys.size()]; + Iterator iterator = keys.iterator(); + + for (int i = 0; i < mKeys.length && iterator.hasNext(); ++i) { + Key key = iterator.next(); + mKeys[i] = new KeyHolder(key); + } + } + + public boolean dismissMiniKeyboard() { + boolean dismiss = false; + if (mMiniKeyboardOnScreen) { + mMiniKeyboardOnScreen = false; + setKeys(mKeyboard.getKeys()); + invalidateAllKeys(); + dismiss = true; + } + + return dismiss; + } + + public int getBaseMiniKbIndex() { + return mBaseMiniKbIndex; + } + + public int getColCount() { + return mColCount; + } + + public Key getFocusedKey() { + return mFocusIndex == -1 ? null : mKeys[mFocusIndex].key; + } + + public Key getKey(int index) { + return mKeys != null && mKeys.length != 0 && index >= 0 && index <= mKeys.length ? mKeys[index].key : null; + } + + public Keyboard getKeyboard() { + return mKeyboard; + } + + /** + * Get index of the key under cursor + *
+ * Resulted index depends on the space key position + * @param x x position + * @param y y position + * @return index of the key + */ + public int getNearestIndex(final float x, final float y) { + int result; + if (mKeys != null && mKeys.length != 0) { + float paddingLeft = (float) getPaddingLeft(); + float paddingTop = (float) getPaddingTop(); + float kbHeight = (float) (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); + float kbWidth = (float) (getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); + final int rows = getRowCount(); + final int cols = getColCount(); + final int indexVert = (int) ((y - paddingTop) / kbHeight * (float) rows); + if (indexVert < 0) { + result = 0; + } else { + result = indexVert; + if (indexVert >= rows) { + result = rows - 1; + } + } + + final int indexHoriz = (int) ((x - paddingLeft) / kbWidth * (float) cols); + int indexFull; + if (indexHoriz < 0) { + indexFull = 0; + } else { + indexFull = indexHoriz; + if (indexHoriz >= cols) { + indexFull = cols - 1; + } + } + + indexFull += mColCount * result; + result = indexFull; + if (indexFull > ASCII_PERIOD) { // key goes beyond space + if (indexFull < (ASCII_PERIOD + ASCII_PERIOD_LEN)) { // key stays within space boundary + result = ASCII_PERIOD; + } + } + + indexFull = result; + if (result >= (ASCII_PERIOD + ASCII_PERIOD_LEN)) { // is key position after space? + indexFull = result - ASCII_PERIOD_LEN + 1; + } + + if (indexFull < 0) { + return 0; + } + + result = indexFull; + if (indexFull >= mKeys.length) { + return mKeys.length - 1; + } + } else { + result = 0; + } + + return result; + } + + public int getRowCount() { + return mRowCount; + } + + public int getShiftState() { + return mShiftState; + } + + public void invalidateAllKeys() { + createKeyImageViews(mKeys); + } + + public void invalidateKey(int keyIndex) { + if (mKeys != null && keyIndex >= 0 && keyIndex < mKeys.length) { + if (mKeyImageViews[keyIndex] != null) { + removeView(mKeyImageViews[keyIndex]); + } + + mKeyImageViews[keyIndex] = createKeyImageView(keyIndex); + } + } + + public boolean isMiniKeyboardOnScreen() { + return mMiniKeyboardOnScreen; + } + + public boolean isShifted() { + return mShiftState == SHIFT_ON || mShiftState == SHIFT_LOCKED; + } + + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + public void onKeyLongPress() { + int popupResId = mKeys[mFocusIndex].key.popupResId; + + if (popupResId != 0) { + dismissMiniKeyboard(); + mMiniKeyboardOnScreen = true; + List accentKeys = (new Keyboard(getContext(), popupResId)).getKeys(); + int totalAccentKeys = accentKeys.size(); + int baseIndex = mFocusIndex; + int currentRow = mFocusIndex / mColCount; + int nextRow = (mFocusIndex + totalAccentKeys) / mColCount; + if (currentRow != nextRow) { + baseIndex = mColCount * nextRow - totalAccentKeys; + } + + mBaseMiniKbIndex = baseIndex; + + for (int i = 0; i < totalAccentKeys; ++i) { + Key accentKey = accentKeys.get(i); + accentKey.x = mKeys[baseIndex + i].key.x; + accentKey.y = mKeys[baseIndex + i].key.y; + accentKey.edgeFlags = mKeys[baseIndex + i].key.edgeFlags; + mKeys[baseIndex + i].key = accentKey; + mKeys[baseIndex + i].isInMiniKb = true; + KeyHolder holder = mKeys[baseIndex + i]; + + holder.isInvertible = i == 0; // uppercase first char + } + + invalidateAllKeys(); + } else { + boolean isSpecialKey = mKeys[mFocusIndex].key.icon != null; // space, paste, voice input etc + + if (!isSpecialKey) { // simply use the same char in uppercase + dismissMiniKeyboard(); + mMiniKeyboardOnScreen = true; + mBaseMiniKbIndex = mFocusIndex; + + mKeys[mFocusIndex].isInMiniKb = true; + mKeys[mFocusIndex].isInvertible = true; + + invalidateAllKeys(); + } + } + } + + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mKeyboard == null) { + setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); + } else { + int heightFull = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); + heightMeasureSpec = heightFull; + if (MeasureSpec.getSize(widthMeasureSpec) < heightFull + 10) { + heightMeasureSpec = MeasureSpec.getSize(widthMeasureSpec); + } + + setMeasuredDimension(heightMeasureSpec, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); + } + } + + public void setFocus(int row, int col, boolean clicked) { + setFocus(mColCount * row + col, clicked); + } + + public void setFocus(int index, boolean clicked) { + setFocus(index, clicked, true); + } + + /** + * NOTE: Increase size of currently focused or clicked key + * @param index index of the key + * @param clicked key state + * @param showFocusScale increase size + */ + public void setFocus(final int index, final boolean clicked, final boolean showFocusScale) { + float scale = 1.0F; + if (mKeyImageViews != null && mKeyImageViews.length != 0) { + int indexFull; + + if (index >= 0 && index < mKeyImageViews.length) { + indexFull = index; + } else { + indexFull = -1; + } + + if (indexFull != mFocusIndex || clicked != mFocusClicked) { + if (indexFull != mFocusIndex) { + if (mFocusIndex != -1) { + LeanbackUtils.sendAccessibilityEvent(mKeyImageViews[mFocusIndex], false); + } + + if (indexFull != -1) { + LeanbackUtils.sendAccessibilityEvent(mKeyImageViews[indexFull], true); + } + } + + if (mCurrentFocusView != null) { + mCurrentFocusView.animate() + .scaleX(scale) + .scaleY(scale) + .setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setStartDelay(mUnfocusStartDelay); + + mCurrentFocusView.animate() + .setDuration(mClickAnimDur) + .setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setStartDelay(mUnfocusStartDelay); + } + + if (indexFull != -1) { + if (clicked) { + scale = mClickedScale; + } else if (showFocusScale) { + scale = mFocusedScale; + } + + mCurrentFocusView = mKeyImageViews[indexFull]; + mCurrentFocusView.animate() + .scaleX(scale) + .scaleY(scale) + .setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setDuration(mClickAnimDur) + .start(); + } + + mFocusIndex = indexFull; + mFocusClicked = clicked; + if (-1 != indexFull && !mKeys[indexFull].isInMiniKb) { + dismissMiniKeyboard(); + } + } + } + + } + + public void setKeyboard(Keyboard keyboard) { + removeMessages(); + mKeyboard = keyboard; + setKeys(mKeyboard.getKeys()); + int state = mShiftState; + mShiftState = -1; + setShiftState(state); + requestLayout(); + invalidateAllKeys(); + } + + /** + * Set keyboard shift sate + * @param state one of the + * {@link LeanbackKeyboardView#SHIFT_ON SHIFT_ON}, + * {@link LeanbackKeyboardView#SHIFT_OFF SHIFT_OFF}, + * {@link LeanbackKeyboardView#SHIFT_LOCKED SHIFT_LOCKED} + * constants + */ + public void setShiftState(int state) { + if (mShiftState != state) { + switch (state) { + case SHIFT_OFF: + mKeyboard.setShifted(false); + break; + case SHIFT_ON: + case SHIFT_LOCKED: + mKeyboard.setShifted(true); + } + + mShiftState = state; + invalidateAllKeys(); + } + } + + private static class KeyHolder { + public boolean isInMiniKb = false; + public boolean isInvertible = false; + public Key key; + + public KeyHolder(Key key) { + this.key = key; + } + } + + public void setCapsLockDrawable(Drawable drawable) { + mCustomCapsLockDrawable = drawable; + } + + public void setKeyTextColor(int color) { + mKeyTextColor = color; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackLocales.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackLocales.java new file mode 100644 index 0000000..f9e1377 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackLocales.java @@ -0,0 +1,92 @@ +package com.liskovsoft.leankeyboard.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/liskovsoft/leankeyboard/ime/LeanbackSuggestionsFactory.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackSuggestionsFactory.java new file mode 100644 index 0000000..44582f0 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackSuggestionsFactory.java @@ -0,0 +1,96 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.inputmethodservice.InputMethodService; +import android.text.InputType; +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 String TAG = "LbSuggestionsFactory"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Use short text tag to fix "Log tag exceeds limit of 23 characters" + private static final int MODE_AUTO_COMPLETE = 2; + private static final int MODE_DEFAULT = 0; + private static final int MODE_DOMAIN = 1; + private InputMethodService mContext; + private int mMode; + private int mNumSuggestions; + private final ArrayList mSuggestions = new ArrayList<>(); + + public LeanbackSuggestionsFactory(InputMethodService context, int numSuggestions) { + mContext = context; + mNumSuggestions = numSuggestions; + } + + public void clearSuggestions() { + mSuggestions.clear(); + mSuggestions.add(null); // make room for user input, see LeanbackKeyboardContainer.addUserInputToSuggestions + } + + public void createSuggestions() { + clearSuggestions(); + if (mMode == MODE_DOMAIN) { + String[] domains = mContext.getResources().getStringArray(R.array.common_domains); + int totalDomains = domains.length; + + for (int i = 0; i < totalDomains; ++i) { + String domain = domains[i]; + mSuggestions.add(domain); + } + } + + } + + public ArrayList getSuggestions() { + return mSuggestions; + } + + public void onDisplayCompletions(CompletionInfo[] infos) { + createSuggestions(); + int len; + if (infos == null) { + len = 0; + } else { + len = infos.length; + } + + for (int i = 0; i < len && mSuggestions.size() < mNumSuggestions && !TextUtils.isEmpty(infos[i].getText()); ++i) { + mSuggestions.add(i, infos[i].getText().toString()); + } + + if (DEBUG) { + for (len = 0; len < mSuggestions.size(); ++len) { + Log.d(TAG, "completion " + len + ": " + mSuggestions.get(len)); + } + } + + } + + public void onStartInput(EditorInfo info) { + mMode = MODE_DEFAULT; + if ((info.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + mMode = MODE_AUTO_COMPLETE; + } + + switch (LeanbackUtils.getInputTypeClass(info)) { + case InputType.TYPE_CLASS_TEXT: + switch (LeanbackUtils.getInputTypeVariation(info)) { + case InputType.TYPE_DATETIME_VARIATION_TIME: + case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS: + mMode = MODE_DOMAIN; + return; + default: + return; + } + default: + } + } + + public boolean shouldSuggestionsAmend() { + return mMode == MODE_DOMAIN; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackUtils.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackUtils.java new file mode 100644 index 0000000..cbd923c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/LeanbackUtils.java @@ -0,0 +1,198 @@ +package com.liskovsoft.leankeyboard.ime; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.text.InputType; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.LinearLayout; +import androidx.core.text.BidiFormatter; +import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer.KeyFocus; +import com.liskovsoft.leankeykeyboard.R; + +public class LeanbackUtils { + private static final int ACCESSIBILITY_DELAY_MS = 250; + private static final String EDITOR_LABEL = "label"; + private static final Handler sAccessibilityHandler = new Handler(); + private static final String TAG = LeanbackUtils.class.getSimpleName(); + + public static int getImeAction(EditorInfo info) { + return info.imeOptions & (EditorInfo.IME_FLAG_NO_ENTER_ACTION | EditorInfo.IME_MASK_ACTION); + } + + /** + * Get class of the input + * @param info attrs + * @return constant e.g. {@link InputType#TYPE_CLASS_TEXT InputType.TYPE_CLASS_TEXT} + */ + public static int getInputTypeClass(EditorInfo info) { + return info.inputType & InputType.TYPE_MASK_CLASS; + } + + /** + * Get variation of the input + * @param info attrs + * @return constant e.g. {@link InputType#TYPE_DATETIME_VARIATION_DATE InputType.TYPE_DATETIME_VARIATION_DATE} + */ + public static int getInputTypeVariation(EditorInfo info) { + return info.inputType & InputType.TYPE_MASK_VARIATION; + } + + public static boolean isAlphabet(int letter) { + return Character.isLetter(letter); + } + + @SuppressLint("NewApi") + public static void sendAccessibilityEvent(final View view, boolean focusGained) { + if (view != null && focusGained) { + sAccessibilityHandler.removeCallbacksAndMessages(null); + sAccessibilityHandler.postDelayed(() -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT), ACCESSIBILITY_DELAY_MS); + } + + } + + public static int getAmpersandLocation(InputConnection connection) { + String text = getEditorText(connection); + int pos = text.indexOf(64); + if (pos < 0) { // not found + pos = text.length(); + } + + return pos; + } + + public static int getCharLengthAfterCursor(InputConnection connection) { + int len = 0; + CharSequence after = connection.getTextAfterCursor(1000, 0); + if (after != null) { + len = after.length(); + } + + return len; + } + + public static int getCharLengthBeforeCursor(InputConnection connection) { + int len = 0; + CharSequence before = connection.getTextBeforeCursor(1000, 0); + if (before != null) { + len = before.length(); + } + + return len; + } + + public static String getEditorText(InputConnection connection) { + StringBuilder result = new StringBuilder(); + CharSequence before = connection.getTextBeforeCursor(1000, 0); + CharSequence after = connection.getTextAfterCursor(1000, 0); + if (before != null) { + result.append(before); + } + + if (after != null) { + result.append(after); + } + + return result.toString(); + } + + public static void sendEnterKey(InputConnection connection) { + connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); + } + + public static String getEditorLabel(EditorInfo info) { + if (info != null && info.extras != null && info.extras.containsKey(EDITOR_LABEL)) { + return info.extras.getString(EDITOR_LABEL); + } + + return null; + } + + public static DisplayMetrics createMetricsFrom(Context context, float factor) { + DisplayMetrics metrics = null; + Object service = context.getSystemService(Context.WINDOW_SERVICE); + + if (service instanceof WindowManager) { + WindowManager manager = (WindowManager) service; + metrics = new DisplayMetrics(); + manager.getDefaultDisplay().getMetrics(metrics); + Log.d(TAG, metrics.toString()); + + // new values + metrics.density *= factor; + metrics.densityDpi *= factor; + metrics.heightPixels *= factor; + metrics.widthPixels *= factor; + metrics.scaledDensity *= factor; + metrics.xdpi *= factor; + metrics.ydpi *= factor; + } + + return metrics; + } + + public static void showKeyboardPicker(Context context) { + if (context != null) { + InputMethodManager imeManager = (InputMethodManager) context.getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imeManager != null) { + imeManager.showInputMethodPicker(); + } + } + } + + public static int getRtlLenAfterCursor(CharSequence text) { + if (text == null || text.length() == 0) { + return 0; + } + + BidiFormatter formatter = BidiFormatter.getInstance(); + int len = 0; + + for (int i = 1; i < text.length(); i++) { + CharSequence charSequence = text.subSequence(len, i); + if (formatter.isRtl(charSequence)) { + len++; + } else { + break; + } + } + + return len; + } + + public static int getRtlLenBeforeCursor(CharSequence text) { + if (text == null || text.length() == 0) { + return 0; + } + + BidiFormatter formatter = BidiFormatter.getInstance(); + int len = 0; + + for (int i = text.length(); i > 0; i--) { + CharSequence charSequence = text.subSequence(i-1, i); + if (formatter.isRtl(charSequence)) { + len++; + } else { + break; + } + } + + return len; + } + + public static boolean isSubmitButton(KeyFocus focus) { + return focus.index == 0 && focus.type == KeyFocus.TYPE_ACTION; + } + + public static boolean isSuggestionsButton(KeyFocus focus) { + return focus.type == KeyFocus.TYPE_SUGGESTION; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavMotionTracker.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavMotionTracker.java new file mode 100644 index 0000000..b0ad731 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavMotionTracker.java @@ -0,0 +1,164 @@ +package com.liskovsoft.leankeyboard.ime.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; + } + + mResolutionX = resolutionX; + if (resolutionY <= 0.0F) { + resolutionY = 6.3F; + } + + mResolutionY = resolutionY; + mMaxFlingVelocityX = mResolutionX * MAXIMUM_FLING_VELOCITY; + mMaxFlingVelocityY = mResolutionY * MAXIMUM_FLING_VELOCITY; + mMinFlingVelocityX = mResolutionX * MINIMUM_FLING_VELOCITY; + mMinFlingVelocityY = mResolutionY * MINIMUM_FLING_VELOCITY; + mMinScrollX = mResolutionX * minScrollDist; + mMinScrollY = 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 event) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + + mVelocityTracker.addMovement(event); + } + + public void clear() { + if (mDownEvent != null) { + mDownEvent.recycle(); + mDownEvent = null; + } + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + + } + + public boolean computeVelocity() { + mVelocityTracker.computeCurrentVelocity(1000); + mVelX = Math.min(mMaxFlingVelocityX, mVelocityTracker.getXVelocity()); + mVelY = Math.min(mMaxFlingVelocityY, mVelocityTracker.getYVelocity()); + return Math.abs(mVelX) > mMinFlingVelocityX || Math.abs(mVelY) > mMinFlingVelocityY; + } + + public MotionEvent getDownEvent() { + return mDownEvent; + } + + public float getPhysicalX(float x) { + return x / mResolutionX; + } + + public float getPhysicalY(float y) { + return y / mResolutionY; + } + + public float getScrollX() { + return mScrollX; + } + + public float getScrollY() { + return mScrollY; + } + + public float getXResolution() { + return mResolutionX; + } + + public float getXVel() { + return mVelX; + } + + public float getYResolution() { + return mResolutionY; + } + + public float getYVel() { + return mVelY; + } + + public void setDownEvent(MotionEvent event) { + if (mDownEvent != null && event != mDownEvent) { + mDownEvent.recycle(); + } + + mDownEvent = event; + } + + public boolean setNewValues(float currX, float currY) { + mCurrX = currX; + mCurrY = currY; + mScrollX = mCurrX - mPrevX; + mScrollY = mCurrY - mPrevY; + return Math.abs(mScrollX) > mMinScrollX || Math.abs(mScrollY) > mMinScrollY; + } + + public void updatePrevValues() { + mPrevX = mCurrX; + mPrevY = mCurrY; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavSpaceTracker.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavSpaceTracker.java new file mode 100644 index 0000000..6ef0f9a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/pano/util/TouchNavSpaceTracker.java @@ -0,0 +1,599 @@ +package com.liskovsoft.leankeyboard.ime.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(null, null); + } + + public TouchNavSpaceTracker(TouchNavSpaceTracker.KeyEventListener keyListener, TouchNavSpaceTracker.TouchEventListener pixelSpaceListener) { + mPrevPhysPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + mPhysicalPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + mWasBlocked = false; + mSensitivityInterpolator = new AccelerateInterpolator(); + mDampingDuration = DEFAULT_DAMPING_DURATION_MS; + mDampedSensitivity = DEFAULT_DAMPED_SENSITIVITY; + mSensitivity = DEFAULT_SENSITIVITY; + mUnscaledFlickMinDistance = DEFAULT_MIN_FLICK_DISTANCE_MM; + mUnscaledFlickMaxDistance = DEFAULT_MAX_FLICK_DISTANCE_MM; + mFlickMinDistance = mSensitivity * DEFAULT_MIN_FLICK_DISTANCE_MM; + mFlickMaxDistance = mSensitivity * DEFAULT_MAX_FLICK_DISTANCE_MM; + mFlickMinSquared = mFlickMinDistance * mFlickMinDistance; + mFlickMaxSquared = mFlickMaxDistance * mFlickMaxDistance; + mFlickMaxDuration = DEFAULT_MAX_FLICK_DURATION_MS; + mLPFEnabled = false; + mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case 0: + if (TouchNavSpaceTracker.this.mKeyEventListener != null) { + TouchNavSpaceTracker.this.mKeyEventListener.onKeyLongPress(msg.arg1, (KeyEvent) msg.obj); + return; + } + default: + } + } + }; + mKeyEventListener = keyListener; + mPixelListener = pixelSpaceListener; + mTouchParams = new SparseArray<>(1); + mPhysicalWidth = DEFAULT_HORIZONTAL_SIZE_MM; + mPhysicalHeight = DEFAULT_VERTICAL_SIZE_MM; + mPixelWidth = 0.0F; + mPixelHeight = 0.0F; + 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 event) { + if (var1 == 23) { + Message msg = this.mHandler.obtainMessage(0); + msg.arg1 = var1; + msg.obj = event; + if (!this.mHandler.hasMessages(0)) { + this.mHandler.sendMessageDelayed(msg, (long) ViewConfiguration.getLongPressTimeout()); + return; + } + } + + } + + private void clampPosition() { + if (mPhysicalPosition.x < 0.0F) { + setPhysicalPosition(0.0F, mPhysicalPosition.y); + } else if (mPhysicalPosition.x > mPhysicalWidth) { + setPhysicalPosition(mPhysicalWidth, mPhysicalPosition.y); + } + + if (mPhysicalPosition.y < 0.0F) { + setPhysicalPosition(mPhysicalPosition.x, 0.0F); + } else if (mPhysicalPosition.y > mPhysicalHeight) { + setPhysicalPosition(mPhysicalPosition.x, mPhysicalHeight); + } + } + + private int getDpadDirection(final float dx, final float dy) { + final float polar = (float) Math.atan2((double) (-dy), (double) dx); + + int idx; + for (idx = 0; idx < DIRECTION_BOUNDARIES.length && polar >= DIRECTION_BOUNDARIES[idx]; ++idx) { + ; + } + + return DIRECTIONS[idx]; + } + + private float getPhysicalX(float x) { + return this.mPixelWidth <= 0.0F ? 0.0F : this.mPhysicalWidth * x / this.mPixelWidth; + } + + private float getPhysicalY(float y) { + return this.mPixelHeight <= 0.0F ? 0.0F : this.mPhysicalHeight * y / this.mPixelHeight; + } + + private float getPixelX(float x) { + return this.mPixelWidth * x / this.mPhysicalWidth; + } + + private float getPixelY(float y) { + return this.mPixelHeight * y / 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 device) { + TouchNavMotionTracker var3 = (TouchNavMotionTracker) this.mTouchParams.get(device.getId()); + TouchNavMotionTracker var2 = var3; + if (var3 == null) { + var2 = TouchNavMotionTracker.buildTrackerForDevice(device, 0.1F); + this.mTouchParams.put(device.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 event) { + if (event != null && (event.getSource() & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { + InputDevice device = event.getDevice(); + if (device == null) { + return false; + } + + TouchNavMotionTracker tracker = this.getTrackerForDevice(device); + int action = event.getActionMasked(); + tracker.addMovement(event); + boolean pointerUp; + if ((action & 255) == MotionEvent.ACTION_POINTER_UP) { + pointerUp = true; + } else { + pointerUp = false; + } + + int skipIndex; + if (pointerUp) { + skipIndex = event.getActionIndex(); + } else { + skipIndex = -1; + } + + float sumX = 0.0F; + float sumY = 0.0F; + int count = event.getPointerCount(); + + for (int i = 0; i < count; ++i) { + if (skipIndex != i) { + sumX += event.getX(i); + sumY += event.getY(i); + } + } + + int div; + if (pointerUp) { + div = count - 1; + } else { + div = count; + } + + float currX = sumX / (float) div; + float currY = sumY / (float) div; + TouchNavSpaceTracker.PhysicalMotionEvent pe = new TouchNavSpaceTracker.PhysicalMotionEvent(event.getDeviceId(), tracker.getPhysicalX + (currX), tracker.getPhysicalX(currY), event.getEventTime()); + boolean var18 = false; + boolean var12 = false; + boolean var11; + MotionEvent var15; + TouchNavSpaceTracker.PhysicalMotionEvent var16; + switch (action & 255) { + case MotionEvent.ACTION_DOWN: + if (mLPFEnabled) { + mLPFCurrX = currX; + mLPFCurrY = currY; + } + + tracker.setNewValues(currX, currY); + tracker.updatePrevValues(); + tracker.setDownEvent(MotionEvent.obtain(event)); + if (mPixelListener != null) { + return mPixelListener.onDown(pe); + } + break; + case MotionEvent.ACTION_UP: + var15 = tracker.getDownEvent(); + if (var15 == null) { + Log.w("TouchNavSpaceTracker", "Up event without down event"); + return false | this.mPixelListener.onUp(pe, this.getPixelX(this.mPhysicalPosition.x), + this.getPixelY(this.mPhysicalPosition.y)); + } + + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(event.getDeviceId(), tracker.getPhysicalX(var15.getX()), tracker.getPhysicalY + (var15.getY()), var15.getEventTime()); + pointerUp = var18; + if (tracker.computeVelocity()) { + pointerUp = var18; + if (this.mPixelListener != null) { + sumY = this.getPixelX(tracker.getPhysicalX(tracker.getXVel())); + sumX = this.getPixelY(tracker.getPhysicalY(tracker.getYVel())); + var18 = false | this.mPixelListener.onFling(var16, pe, sumY, sumX); + pointerUp = var18; + if (pe.getTime() - var16.getTime() < this.mFlickMaxDuration) { + sumY = pe.getX() - var16.getX(); + sumX = pe.getY() - var16.getY(); + currX = sumY * sumY + sumX * sumX; + pointerUp = var18; + if (currX > this.mFlickMinSquared) { + pointerUp = var18; + if (currX < this.mFlickMaxSquared) { + this.mPixelListener.onFlick(var16, pe, this.getDpadDirection(sumY, sumX), this.getPrimaryDpadDirection + (sumY, sumX)); + pointerUp = var18; + } + } + } + } + } + + sumY = this.getPixelX(this.mPhysicalPosition.x); + sumX = this.getPixelY(this.mPhysicalPosition.y); + var11 = this.mPixelListener.onUp(pe, sumY, sumX); + tracker.clear(); + return pointerUp | var11; + case MotionEvent.ACTION_MOVE: + if (tracker.getDownEvent() == null) { + tracker.setDownEvent(MotionEvent.obtain(event)); + if (this.mLPFEnabled) { + this.mLPFCurrX = currX; + this.mLPFCurrY = currY; + } + } + + sumX = currX; + sumY = currY; + if (this.mLPFEnabled) { + this.mLPFCurrX = this.mLPFCurrX * 0.75F + DEFAULT_LPF_COEFF * currX; + this.mLPFCurrY = this.mLPFCurrY * 0.75F + DEFAULT_LPF_COEFF * currY; + sumX = this.mLPFCurrX; + sumY = this.mLPFCurrY; + } + + if (tracker.setNewValues(sumX, sumY)) { + sumY = tracker.getPhysicalX(tracker.getScrollX()); + sumX = tracker.getPhysicalY(tracker.getScrollY()); + currX = this.calculateSensitivity(event, tracker.getDownEvent()); + this.mPhysicalPosition.x = this.mPrevPhysPosition.x + this.getScaledValue(sumY, currX); + this.mPhysicalPosition.y = this.mPrevPhysPosition.y + this.getScaledValue(sumX, currX); + 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 = tracker.getDownEvent(); + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(event.getDeviceId(), tracker.getPhysicalX(var15.getX()), + tracker.getPhysicalY(var15.getY()), var15.getEventTime()); + sumY = this.getPixelX(this.mPhysicalPosition.x); + sumX = this.getPixelY(this.mPhysicalPosition.y); + var11 = false | this.mPixelListener.onMove(var16, pe, sumY, sumX); + } + } + } + + this.mPrevPhysPosition.set(this.mPhysicalPosition); + } else { + var11 = false | true; + } + + tracker.updatePrevValues(); + return var11; + } + + return false | true; + case MotionEvent.ACTION_CANCEL: + tracker.clear(); + return false; + default: + return false; + } + } + + return false; + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (event != null && event.getDevice() != null && (event.getDevice().getSources() & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice + .SOURCE_TOUCH_NAVIGATION) { + if (event.getRepeatCount() == 0) { + checkForLongClick(keyCode, event); + } + + if (mKeyEventListener != null) { + return mKeyEventListener.onKeyDown(keyCode, event); + } + } + + return false; + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (event != null && event.getDevice() != null && (event.getDevice().getSources() & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice + .SOURCE_TOUCH_NAVIGATION) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + mHandler.removeMessages(0); + } + + if (mKeyEventListener != null) { + return mKeyEventListener.onKeyUp(keyCode, event); + } + } + + return false; + } + + public void onPause() { + mHandler.removeMessages(0); + } + + public void setKeyEventListener(TouchNavSpaceTracker.KeyEventListener listener) { + mKeyEventListener = listener; + } + + public void setLPFEnabled(boolean enabled) { + mLPFEnabled = enabled; + } + + public void setPhysicalDensity(float density) { + mPixelsPerMm = density; + if (density > 0.0F) { + updatePhysicalSize(); + } + + } + + public void setPhysicalPosition(float x, float y) { + mPhysicalPosition.x = x; + mPhysicalPosition.y = y; + mPrevPhysPosition.x = x; + mPrevPhysPosition.y = y; + clampPosition(); + } + + public void setPhysicalSize(float widthMm, float heightMm) { + if (mPixelsPerMm <= 0.0F) { + setPhysicalSizeInternal(widthMm, heightMm); + } + } + + public void setPixelPosition(float x, float y) { + setPhysicalPosition(getPhysicalX(x), getPhysicalY(y)); + } + + public void setPixelSize(float width, float height) { + mPixelHeight = height; + mPixelWidth = width; + updatePhysicalSize(); + } + + public void setSensitivity(float sensitivity) { + mSensitivity = sensitivity; + configureFlicks(mUnscaledFlickMinDistance, mUnscaledFlickMaxDistance, mFlickMaxDuration); + } + + public void setTouchEventListener(TouchNavSpaceTracker.TouchEventListener listener) { + mPixelListener = listener; + } + + public void unblockMovement() { + this.mMovementBlockTime = 0L; + } + + public interface KeyEventListener { + boolean onKeyDown(int keyCode, KeyEvent event); + + boolean onKeyLongPress(int keyCode, KeyEvent event); + + boolean onKeyUp(int keyCode, KeyEvent event); + } + + 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 keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + 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/liskovsoft/leankeyboard/ime/voice/BitmapSoundLevelView.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/BitmapSoundLevelView.java new file mode 100644 index 0000000..49a205a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/BitmapSoundLevelView.java @@ -0,0 +1,191 @@ +package com.liskovsoft.leankeyboard.ime.voice; + +import android.animation.TimeAnimator; +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.Paint.Style; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import androidx.core.content.ContextCompat; +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); + mEmptyPaint = new Paint(); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BitmapSoundLevelView, defStyleAttr, 0); + mEnableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_enabledBackgroundColor, Color.parseColor("#66FFFFFF")); + 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; + } + + mCenterTranslationX = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterX, 0); + mCenterTranslationY = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterY, 0); + mMinimumLevelSize = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_minLevelRadius, 0); + styledAttrs.recycle(); + if (primaryLevelEnabled) { + mPrimaryLevel = BitmapFactory.decodeResource(getResources(), primaryLevelId); + } else { + mPrimaryLevel = null; + } + + if (peakLevelEnabled) { + mTrailLevel = BitmapFactory.decodeResource(getResources(), trailLevelId); + } else { + mTrailLevel = null; + } + + mPaint = new Paint(); + mDestRect = new Rect(); + mEmptyPaint.setFilterBitmap(true); + mLevelSource = new SpeechLevelSource(); + mLevelSource.setSpeechLevel(0); + mAnimator = new TimeAnimator(); + mAnimator.setRepeatCount(-1); + mAnimator.setTimeListener((animation, totalTime, deltaTime) -> invalidate()); + } + + @TargetApi(16) + private void startAnimator() { + if (!mAnimator.isStarted()) { + mAnimator.start(); + } + + } + + @TargetApi(16) + private void stopAnimator() { + mAnimator.cancel(); + } + + private void updateAnimatorState() { + if (isEnabled()) { + startAnimator(); + } else { + stopAnimator(); + } + } + + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateAnimatorState(); + } + + protected void onDetachedFromWindow() { + stopAnimator(); + super.onDetachedFromWindow(); + } + + public void onDraw(Canvas canvas) { + if (isEnabled()) { + canvas.drawColor(mEnableBackgroundColor); + final int level = mLevelSource.getSpeechLevel(); + if (level > mPeakLevel) { + mPeakLevel = level; + mPeakLevelCountDown = 25; + } else if (mPeakLevelCountDown == 0) { + mPeakLevel = Math.max(0, mPeakLevel - 2); + } else { + --mPeakLevelCountDown; + } + + if (level > mCurrentVolume) { + mCurrentVolume += (level - mCurrentVolume) / 4; + } else { + mCurrentVolume = (int) ((float) mCurrentVolume * 0.95F); + } + + final int centerX = mCenterTranslationX + getWidth() / 2; + final int centerY = mCenterTranslationY + getWidth() / 2; + int size; + if (mTrailLevel != null) { + size = (centerX - mMinimumLevelSize) * mPeakLevel / 100 + mMinimumLevelSize; + mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(mTrailLevel, null, mDestRect, mEmptyPaint); + } + + if (mPrimaryLevel != null) { + size = (centerX - mMinimumLevelSize) * mCurrentVolume / 100 + mMinimumLevelSize; + mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(mPrimaryLevel, null, mDestRect, mEmptyPaint); + mPaint.setColor(ContextCompat.getColor(getContext(), R.color.search_mic_background)); + mPaint.setStyle(Style.FILL); + canvas.drawCircle((float) centerX, (float) centerY, (float) (mMinimumLevelSize - 3), mPaint); + } + + if (mTrailLevel != null && mPrimaryLevel != null) { + mPaint.setColor(ContextCompat.getColor(getContext(), R.color.search_mic_levels_guideline)); + mPaint.setStyle(Style.STROKE); + canvas.drawCircle((float) centerX, (float) centerY, (float) (centerX - 13), mPaint); + } + + } else { + canvas.drawColor(mDisableBackgroundColor); + } + } + + public void onWindowFocusChanged(boolean var1) { + super.onWindowFocusChanged(var1); + if (var1) { + updateAnimatorState(); + } else { + stopAnimator(); + } + } + + public void setEnabled(boolean var1) { + super.setEnabled(var1); + updateAnimatorState(); + } + + public void setLevelSource(SpeechLevelSource var1) { + mLevelSource = var1; + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/RecognizerView.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/RecognizerView.java new file mode 100644 index 0000000..6e5d50c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/RecognizerView.java @@ -0,0 +1,214 @@ +package com.liskovsoft.leankeyboard.ime.voice; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import com.liskovsoft.leankeyboard.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; + + private enum State { + LISTENING, MIC_INITIALIZING, NOT_LISTENING, RECOGNIZING, RECORDING; + } + + public RecognizerView(Context context) { + super(context); + } + + public RecognizerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public RecognizerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private void updateState(State state) { + mState = state; + refreshUi(); + } + + public View getMicButton() { + return mMicButton; + } + + public void onAttachedToWindow() { + super.onAttachedToWindow(); + refreshUi(); + } + + public void onClick() { + switch (mState) { + case MIC_INITIALIZING: + default: + return; + case LISTENING: + mCallback.onCancelRecordingClicked(); + return; + case RECORDING: + mCallback.onStopRecordingClicked(); + return; + case RECOGNIZING: + mCallback.onCancelRecordingClicked(); + return; + case NOT_LISTENING: + mCallback.onStartRecordingClicked(); + } + } + + @SuppressLint("MissingSuperCall") + @Override + public void onFinishInflate() { + LayoutInflater.from(this.getContext()).inflate(R.layout.recognizer_view, this, true); + mSoundLevels = (BitmapSoundLevelView) findViewById(R.id.microphone); + mMicButton = (ImageView) findViewById(R.id.recognizer_mic_button); + mState = RecognizerView.State.NOT_LISTENING; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof RecognizerView.SavedState)) { + super.onRestoreInstanceState(state); + } else { + RecognizerView.SavedState savedState = (RecognizerView.SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + mState = savedState.mState; + } + } + + @Override + public Parcelable onSaveInstanceState() { + RecognizerView.SavedState savedState = new RecognizerView.SavedState(super.onSaveInstanceState()); + savedState.mState = mState; + return savedState; + } + + protected void refreshUi() { + if (mEnabled) { + switch (mState) { + case MIC_INITIALIZING: + mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + mSoundLevels.setEnabled(false); + return; + case LISTENING: + mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + mSoundLevels.setEnabled(true); + return; + case RECORDING: + mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector); + mSoundLevels.setEnabled(true); + return; + case RECOGNIZING: + mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + mSoundLevels.setEnabled(false); + return; + case NOT_LISTENING: + mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + mSoundLevels.setEnabled(false); + return; + default: + } + } + } + + public void setCallback(RecognizerView.Callback callback) { + mCallback = callback; + } + + public void setMicEnabled(boolean enabled) { + mEnabled = enabled; + if (enabled) { + mMicButton.setAlpha(1.0F); + mMicButton.setImageResource(R.drawable.ic_voice_available); + } else { + mMicButton.setAlpha(0.1F); + mMicButton.setImageResource(R.drawable.ic_voice_off); + } + } + + public void setMicFocused(boolean focused) { + if (mEnabled) { + if (focused) { + mMicButton.setImageResource(R.drawable.ic_voice_focus); + } else { + mMicButton.setImageResource(R.drawable.ic_voice_available); + } + + LeanbackUtils.sendAccessibilityEvent(mMicButton, focused); + } + + } + + public void setSpeechLevelSource(SpeechLevelSource var1) { + mSoundLevels.setLevelSource(var1); + } + + public void showInitializingMic() { + updateState(State.MIC_INITIALIZING); + } + + public void showListening() { + updateState(State.LISTENING); + } + + public void showNotListening() { + updateState(State.NOT_LISTENING); + } + + public void showRecognizing() { + updateState(State.RECOGNIZING); + } + + public void showRecording() { + updateState(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()); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/SpeechLevelSource.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/SpeechLevelSource.java new file mode 100644 index 0000000..d60cd05 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/ime/voice/SpeechLevelSource.java @@ -0,0 +1,25 @@ +package com.liskovsoft.leankeyboard.ime.voice; + +public class SpeechLevelSource { + private volatile int mSpeechLevel; + + public int getSpeechLevel() { + return mSpeechLevel; + } + + public boolean isValid() { + return mSpeechLevel > 0; + } + + public void reset() { + mSpeechLevel = -1; + } + + public void setSpeechLevel(int speechLevel) { + if (speechLevel >= 0 && speechLevel <= 100) { + mSpeechLevel = speechLevel; + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/RestartServiceReceiver.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/RestartServiceReceiver.java new file mode 100644 index 0000000..51cd13b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/RestartServiceReceiver.java @@ -0,0 +1,28 @@ +package com.liskovsoft.leankeyboard.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import com.liskovsoft.leankeyboard.ime.LeanbackImeService; + +public class RestartServiceReceiver extends BroadcastReceiver { + private static final String TAG = RestartServiceReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + sendMessageToService(context); + //restartService(context); + } + + private void sendMessageToService(Context context) { + Log.d(TAG, "Sending restart message to the service"); + Intent intent = new Intent(context, LeanbackImeService.class); + intent.putExtra(LeanbackImeService.COMMAND_RESTART, true); + context.startService(intent); + } + + private void restartService(Context context) { + System.exit(0); + } +} \ No newline at end of file diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/TextUpdateReceiver.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/TextUpdateReceiver.java new file mode 100644 index 0000000..7fd403f --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/receiver/TextUpdateReceiver.java @@ -0,0 +1,15 @@ +package com.liskovsoft.leankeyboard.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class TextUpdateReceiver extends BroadcastReceiver { + private static final String TAG = TextUpdateReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, intent.toUri(0)); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LangUpdater.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LangUpdater.java new file mode 100644 index 0000000..e7301ca --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LangUpdater.java @@ -0,0 +1,124 @@ +package com.liskovsoft.leankeyboard.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import com.liskovsoft.leankeyboard.helpers.Helpers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.StringTokenizer; + +public class LangUpdater { + private static final String LOCALE_EN_US = "en_US"; + private static final String LOCALE_RU = "ru_RU"; + private Context mContext; + private String[] rusPackages = {"dkc.androidtv.tree", "dkc.video.fsbox", "dkc.video.hdbox", "dkc.video.uatv"}; + + public LangUpdater(Context ctx) { + mContext = ctx; + } + + public void update() { + tryToEnableRussian(); + tryToForceEnglishOnDevices(); + tryToRestoreLanguage(); + } + + private void tryToRestoreLanguage() { + String langCode = getPreferredLocale(); + if (langCode != null) { + forceLocale(langCode); + } + } + + private void tryToForceEnglishOnDevices() { + String deviceName = Helpers.getDeviceName(); + switch (deviceName) { + case "ChangHong Android TV (full_mst638)": + forceLocale(LOCALE_EN_US); + } + } + + private void tryToBypassChinese() { + String script = LocaleUtility.getScript(Locale.getDefault()); + if (isChineseScript(script)) { + forceLocale(LOCALE_EN_US); + } + } + + private boolean isChineseScript(String script) { + switch (script) { + case "Hani": + case "Hans": + case "Hant": + return true; + } + return false; + } + + private void tryToEnableRussian() { + List installedPackages = getListInstalledPackages(); + for (String pkgName : installedPackages) { + if (isRussianPackage(pkgName)) { + forceLocale(LOCALE_RU); + } + } + } + + // short lang code. ex: "ru" + private void forceLocale(String langCode) { + if (langCode == null || langCode.isEmpty()) { + return; + } + + Locale locale = parseLangCode(langCode); + LocaleUtility.forceLocaleOld(mContext, locale); + } + + private boolean isRussianPackage(String pkgName) { + for (String rusPackage : rusPackages) { + if (rusPackage.equals(pkgName)){ + return true; + } + } + return false; + } + + private List getListInstalledPackages() { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List pkgAppsList = mContext.getPackageManager().queryIntentActivities( mainIntent, 0); + List result = new ArrayList<>(); + for (ResolveInfo info : pkgAppsList) { + result.add(info.activityInfo.packageName); + } + return result; + } + + public String getLocale() { + Configuration config = mContext.getResources().getConfiguration(); + return LocaleUtility.getSystemLocale(config).getLanguage(); + } + + /** + * Get locale as lang code (e.g. zh, ru_RU etc) + * @return lang code + */ + public String getPreferredLocale() { + return LeanKeyPreferences.instance(mContext).getPreferredLanguage(); + } + + public void setPreferredLocale(String langCode) { + LeanKeyPreferences.instance(mContext).setPreferredLanguage(langCode); + } + + private Locale parseLangCode(String langCode) { + StringTokenizer tokenizer = new StringTokenizer(langCode, "_"); + String lang = tokenizer.nextToken(); + String country = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : ""; + return new Locale(lang, country); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LeanKeyPreferences.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LeanKeyPreferences.java new file mode 100644 index 0000000..b2c4aaa --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LeanKeyPreferences.java @@ -0,0 +1,119 @@ +package com.liskovsoft.leankeyboard.utils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public final class LeanKeyPreferences { + private static final String APP_RUN_ONCE = "appRunOnce"; + private static final String BOOTSTRAP_SELECTED_LANGUAGE = "bootstrapSelectedLanguage"; + private static final String APP_KEYBOARD_INDEX = "appKeyboardIndex"; + private static final String FORCE_SHOW_KEYBOARD = "forceShowKeyboard"; + private static final String ENLARGE_KEYBOARD = "enlargeKeyboard"; + private static final String KEYBOARD_THEME = "keyboardTheme"; + public static final String THEME_DEFAULT = "Default"; + public static final String THEME_DARK = "Dark"; + public static final String THEME_DARK2 = "Dark2"; + public static final String THEME_DARK3 = "Dark3"; + private static final String SUGGESTIONS_ENABLED = "suggestionsEnabled"; + private static final String CYCLIC_NAVIGATION_ENABLED = "cyclicNavigationEnabled"; + private static final String AUTODETECT_LAYOUT = "autodetectLayout"; + private static LeanKeyPreferences sInstance; + private final Context mContext; + private SharedPreferences mPrefs; + + public static LeanKeyPreferences instance(Context ctx) { + if (sInstance == null) + sInstance = new LeanKeyPreferences(ctx); + return sInstance; + } + + public LeanKeyPreferences(Context context) { + mContext = context.getApplicationContext(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + } + + public boolean isRunOnce() { + return mPrefs.getBoolean(APP_RUN_ONCE, false); + } + + public void setRunOnce(boolean runOnce) { + mPrefs.edit() + .putBoolean(APP_RUN_ONCE, runOnce) + .apply(); + } + + public void setPreferredLanguage(String name) { + mPrefs.edit() + .putString(BOOTSTRAP_SELECTED_LANGUAGE, name) + .apply(); + } + + public String getPreferredLanguage() { + return mPrefs.getString(BOOTSTRAP_SELECTED_LANGUAGE, ""); + } + + public int getKeyboardIndex() { + return mPrefs.getInt(APP_KEYBOARD_INDEX, 0); + } + + public void setKeyboardIndex(int idx) { + mPrefs.edit() + .putInt(APP_KEYBOARD_INDEX, idx) + .apply(); + } + + public boolean getForceShowKeyboard() { + return mPrefs.getBoolean(FORCE_SHOW_KEYBOARD, true); + } + + public void setForceShowKeyboard(boolean force) { + mPrefs.edit() + .putBoolean(FORCE_SHOW_KEYBOARD, force) + .apply(); + } + + public boolean getEnlargeKeyboard() { + return mPrefs.getBoolean(ENLARGE_KEYBOARD, false); + } + + public void setEnlargeKeyboard(boolean enlarge) { + mPrefs.edit() + .putBoolean(ENLARGE_KEYBOARD, enlarge) + .apply(); + } + + public void setCurrentTheme(String theme) { + mPrefs.edit() + .putString(KEYBOARD_THEME, theme) + .apply(); + } + + public String getCurrentTheme() { + return mPrefs.getString(KEYBOARD_THEME, THEME_DARK3); + } + + public void setSuggestionsEnabled(boolean enabled) { + mPrefs.edit() + .putBoolean(SUGGESTIONS_ENABLED, enabled) + .apply(); + } + + public boolean getSuggestionsEnabled() { + return mPrefs.getBoolean(SUGGESTIONS_ENABLED, true); + } + + public void setCyclicNavigationEnabled(boolean enabled) { + mPrefs.edit() + .putBoolean(CYCLIC_NAVIGATION_ENABLED, enabled) + .apply(); + } + + public boolean isCyclicNavigationEnabled() { + return mPrefs.getBoolean(CYCLIC_NAVIGATION_ENABLED, false); + } + + public boolean getAutodetectLayout() { + return mPrefs.getBoolean(AUTODETECT_LAYOUT, false); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleScript.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleScript.java new file mode 100644 index 0000000..1c9e5e4 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleScript.java @@ -0,0 +1,727 @@ +package com.liskovsoft.leankeyboard.utils; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/* + * Additional info: + * https://en.wikipedia.org/wiki/Writing_system + * https://stackoverflow.com/questions/19153384/how-to-get-the-script-from-a-locale-object-on-android + * https://docs.oracle.com/javase/7/docs/api/java/util/Locale.html#getScript() + * http://unicode.org/iso15924/iso15924-codes.html + * + * Usage: + * String script = LocaleScript.getScript(Locale.getDefault()); + * String script = LocaleScript.getScript(getDefaultLocale(myActivity)); + */ +class LocaleScript { + + public static Map> scriptsByLocale = new HashMap>(); + + public static Map getScriptsMap(String... keyValuePairs) { + Map languages = new HashMap(); + for (int i = 0; i < keyValuePairs.length; i += 2) { + languages.put(keyValuePairs[i], keyValuePairs[i + 1]); + } + return languages; + } + + static { + scriptsByLocale.put("aa", getScriptsMap("", "Latn")); + scriptsByLocale.put("ab", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("abq", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("abr", getScriptsMap("", "")); + scriptsByLocale.put("ace", getScriptsMap("", "Latn")); + scriptsByLocale.put("ach", getScriptsMap("", "Latn")); + scriptsByLocale.put("ada", getScriptsMap("", "Latn")); + scriptsByLocale.put("ady", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ae", getScriptsMap("", "Avst")); + scriptsByLocale.put("af", getScriptsMap("", "Latn")); + scriptsByLocale.put("agq", getScriptsMap("", "Latn")); + scriptsByLocale.put("aii", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ain", getScriptsMap("", "Kana")); + scriptsByLocale.put("ak", getScriptsMap("", "Latn")); + scriptsByLocale.put("akk", getScriptsMap("", "Xsux")); + scriptsByLocale.put("ale", getScriptsMap("", "Latn")); + scriptsByLocale.put("alt", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("am", getScriptsMap("", "Ethi")); + scriptsByLocale.put("amo", getScriptsMap("", "Latn")); + scriptsByLocale.put("an", getScriptsMap("", "Latn")); + scriptsByLocale.put("anp", getScriptsMap("", "Deva")); + scriptsByLocale.put("aoz", getScriptsMap("", "")); + scriptsByLocale.put("ar", getScriptsMap("", "Arab", "IR", "Syrc")); + scriptsByLocale.put("arc", getScriptsMap("", "Armi")); + scriptsByLocale.put("arn", getScriptsMap("", "Latn")); + scriptsByLocale.put("arp", getScriptsMap("", "Latn")); + scriptsByLocale.put("arw", getScriptsMap("", "Latn")); + scriptsByLocale.put("as", getScriptsMap("", "Beng")); + scriptsByLocale.put("asa", getScriptsMap("", "Latn")); + scriptsByLocale.put("ast", getScriptsMap("", "Latn")); + scriptsByLocale.put("atj", getScriptsMap("", "")); + scriptsByLocale.put("av", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("awa", getScriptsMap("", "Deva")); + scriptsByLocale.put("ay", getScriptsMap("", "Latn")); + scriptsByLocale.put("az", getScriptsMap("", "Latn", "AZ", "Cyrl", "IR", "Arab")); + scriptsByLocale.put("ba", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("bal", getScriptsMap("", "Arab", "IR", "Latn", "PK", "Latn")); + scriptsByLocale.put("ban", getScriptsMap("", "Latn", "ID", "Bali")); + scriptsByLocale.put("bap", getScriptsMap("", "")); + scriptsByLocale.put("bas", getScriptsMap("", "Latn")); + scriptsByLocale.put("bax", getScriptsMap("", "Bamu")); + scriptsByLocale.put("bbc", getScriptsMap("", "Latn", "ID", "Batk")); + scriptsByLocale.put("bbj", getScriptsMap("", "")); + scriptsByLocale.put("bci", getScriptsMap("", "")); + scriptsByLocale.put("be", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("bej", getScriptsMap("", "Arab")); + scriptsByLocale.put("bem", getScriptsMap("", "Latn")); + scriptsByLocale.put("bew", getScriptsMap("", "")); + scriptsByLocale.put("bez", getScriptsMap("", "Latn")); + scriptsByLocale.put("bfd", getScriptsMap("", "")); + scriptsByLocale.put("bfq", getScriptsMap("", "Taml")); + scriptsByLocale.put("bft", getScriptsMap("", "Arab")); + scriptsByLocale.put("bfy", getScriptsMap("", "Deva")); + scriptsByLocale.put("bg", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("bgc", getScriptsMap("", "")); + scriptsByLocale.put("bgx", getScriptsMap("", "")); + scriptsByLocale.put("bh", getScriptsMap("", "Deva")); + scriptsByLocale.put("bhb", getScriptsMap("", "Deva")); + scriptsByLocale.put("bhi", getScriptsMap("", "")); + scriptsByLocale.put("bhk", getScriptsMap("", "")); + scriptsByLocale.put("bho", getScriptsMap("", "Deva")); + scriptsByLocale.put("bi", getScriptsMap("", "Latn")); + scriptsByLocale.put("bik", getScriptsMap("", "Latn")); + scriptsByLocale.put("bin", getScriptsMap("", "Latn")); + scriptsByLocale.put("bjj", getScriptsMap("", "Deva")); + scriptsByLocale.put("bjn", getScriptsMap("", "")); + scriptsByLocale.put("bkm", getScriptsMap("", "")); + scriptsByLocale.put("bku", getScriptsMap("", "Latn")); + scriptsByLocale.put("bla", getScriptsMap("", "Latn")); + scriptsByLocale.put("blt", getScriptsMap("", "Tavt")); + scriptsByLocale.put("bm", getScriptsMap("", "Latn")); + scriptsByLocale.put("bmq", getScriptsMap("", "")); + scriptsByLocale.put("bn", getScriptsMap("", "Beng")); + scriptsByLocale.put("bo", getScriptsMap("", "Tibt")); + scriptsByLocale.put("bqi", getScriptsMap("", "")); + scriptsByLocale.put("bqv", getScriptsMap("", "Latn")); + scriptsByLocale.put("br", getScriptsMap("", "Latn")); + scriptsByLocale.put("bra", getScriptsMap("", "Deva")); + scriptsByLocale.put("brh", getScriptsMap("", "")); + scriptsByLocale.put("brx", getScriptsMap("", "Deva")); + scriptsByLocale.put("bs", getScriptsMap("", "Latn")); + scriptsByLocale.put("bss", getScriptsMap("", "")); + scriptsByLocale.put("bto", getScriptsMap("", "")); + scriptsByLocale.put("btv", getScriptsMap("", "Deva")); + scriptsByLocale.put("bua", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("buc", getScriptsMap("", "Latn")); + scriptsByLocale.put("bug", getScriptsMap("", "Latn", "ID", "Bugi")); + scriptsByLocale.put("bum", getScriptsMap("", "")); + scriptsByLocale.put("bvb", getScriptsMap("", "")); + scriptsByLocale.put("bya", getScriptsMap("", "Latn")); + scriptsByLocale.put("byn", getScriptsMap("", "Ethi")); + scriptsByLocale.put("byv", getScriptsMap("", "")); + scriptsByLocale.put("bze", getScriptsMap("", "")); + scriptsByLocale.put("bzx", getScriptsMap("", "")); + scriptsByLocale.put("ca", getScriptsMap("", "Latn")); + scriptsByLocale.put("cad", getScriptsMap("", "Latn")); + scriptsByLocale.put("car", getScriptsMap("", "Latn")); + scriptsByLocale.put("cay", getScriptsMap("", "Latn")); + scriptsByLocale.put("cch", getScriptsMap("", "Latn")); + scriptsByLocale.put("ccp", getScriptsMap("", "Beng")); + scriptsByLocale.put("ce", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ceb", getScriptsMap("", "Latn")); + scriptsByLocale.put("cgg", getScriptsMap("", "Latn")); + scriptsByLocale.put("ch", getScriptsMap("", "Latn")); + scriptsByLocale.put("chk", getScriptsMap("", "Latn")); + scriptsByLocale.put("chm", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("chn", getScriptsMap("", "Latn")); + scriptsByLocale.put("cho", getScriptsMap("", "Latn")); + scriptsByLocale.put("chp", getScriptsMap("", "Latn")); + scriptsByLocale.put("chr", getScriptsMap("", "Cher")); + scriptsByLocale.put("chy", getScriptsMap("", "Latn")); + scriptsByLocale.put("cja", getScriptsMap("", "Arab")); + scriptsByLocale.put("cjm", getScriptsMap("", "Cham")); + scriptsByLocale.put("cjs", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ckb", getScriptsMap("", "Arab")); + scriptsByLocale.put("ckt", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("co", getScriptsMap("", "Latn")); + scriptsByLocale.put("cop", getScriptsMap("", "Arab")); + scriptsByLocale.put("cpe", getScriptsMap("", "Latn")); + scriptsByLocale.put("cr", getScriptsMap("", "Cans")); + scriptsByLocale.put("crh", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("crj", getScriptsMap("", "")); + scriptsByLocale.put("crk", getScriptsMap("", "Cans")); + scriptsByLocale.put("crl", getScriptsMap("", "")); + scriptsByLocale.put("crm", getScriptsMap("", "")); + scriptsByLocale.put("crs", getScriptsMap("", "")); + scriptsByLocale.put("cs", getScriptsMap("", "Latn")); + scriptsByLocale.put("csb", getScriptsMap("", "Latn")); + scriptsByLocale.put("csw", getScriptsMap("", "")); + scriptsByLocale.put("cu", getScriptsMap("", "Glag")); + scriptsByLocale.put("cv", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("cy", getScriptsMap("", "Latn")); + scriptsByLocale.put("da", getScriptsMap("", "Latn")); + scriptsByLocale.put("daf", getScriptsMap("", "")); + scriptsByLocale.put("dak", getScriptsMap("", "Latn")); + scriptsByLocale.put("dar", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("dav", getScriptsMap("", "Latn")); + scriptsByLocale.put("dcc", getScriptsMap("", "")); + scriptsByLocale.put("de", getScriptsMap("", "Latn", "BR", "Runr", "KZ", "Runr", "US", "Runr")); + scriptsByLocale.put("del", getScriptsMap("", "Latn")); + scriptsByLocale.put("den", getScriptsMap("", "Latn")); + scriptsByLocale.put("dgr", getScriptsMap("", "Latn")); + scriptsByLocale.put("din", getScriptsMap("", "Latn")); + scriptsByLocale.put("dje", getScriptsMap("", "Latn")); + scriptsByLocale.put("dng", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("doi", getScriptsMap("", "Arab")); + scriptsByLocale.put("dsb", getScriptsMap("", "Latn")); + scriptsByLocale.put("dtm", getScriptsMap("", "")); + scriptsByLocale.put("dua", getScriptsMap("", "Latn")); + scriptsByLocale.put("dv", getScriptsMap("", "Thaa")); + scriptsByLocale.put("dyo", getScriptsMap("", "Arab")); + scriptsByLocale.put("dyu", getScriptsMap("", "Latn")); + scriptsByLocale.put("dz", getScriptsMap("", "Tibt")); + scriptsByLocale.put("ebu", getScriptsMap("", "Latn")); + scriptsByLocale.put("ee", getScriptsMap("", "Latn")); + scriptsByLocale.put("efi", getScriptsMap("", "Latn")); + scriptsByLocale.put("egy", getScriptsMap("", "Egyp")); + scriptsByLocale.put("eka", getScriptsMap("", "Latn")); + scriptsByLocale.put("eky", getScriptsMap("", "Kali")); + scriptsByLocale.put("el", getScriptsMap("", "Grek")); + scriptsByLocale.put("en", getScriptsMap("", "Latn")); + scriptsByLocale.put("eo", getScriptsMap("", "Latn")); + scriptsByLocale.put("es", getScriptsMap("", "Latn")); + scriptsByLocale.put("et", getScriptsMap("", "Latn")); + scriptsByLocale.put("ett", getScriptsMap("", "Ital")); + scriptsByLocale.put("eu", getScriptsMap("", "Latn")); + scriptsByLocale.put("evn", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ewo", getScriptsMap("", "Latn")); + scriptsByLocale.put("fa", getScriptsMap("", "Arab")); + scriptsByLocale.put("fan", getScriptsMap("", "Latn")); + scriptsByLocale.put("ff", getScriptsMap("", "Latn")); + scriptsByLocale.put("ffm", getScriptsMap("", "")); + scriptsByLocale.put("fi", getScriptsMap("", "Latn")); + scriptsByLocale.put("fil", getScriptsMap("", "Latn", "US", "Tglg")); + scriptsByLocale.put("fiu", getScriptsMap("", "Latn")); + scriptsByLocale.put("fj", getScriptsMap("", "Latn")); + scriptsByLocale.put("fo", getScriptsMap("", "Latn")); + scriptsByLocale.put("fon", getScriptsMap("", "Latn")); + scriptsByLocale.put("fr", getScriptsMap("", "Latn")); + scriptsByLocale.put("frr", getScriptsMap("", "Latn")); + scriptsByLocale.put("frs", getScriptsMap("", "Latn")); + scriptsByLocale.put("fud", getScriptsMap("", "")); + scriptsByLocale.put("fuq", getScriptsMap("", "")); + scriptsByLocale.put("fur", getScriptsMap("", "Latn")); + scriptsByLocale.put("fuv", getScriptsMap("", "")); + scriptsByLocale.put("fy", getScriptsMap("", "Latn")); + scriptsByLocale.put("ga", getScriptsMap("", "Latn")); + scriptsByLocale.put("gaa", getScriptsMap("", "Latn")); + scriptsByLocale.put("gag", getScriptsMap("", "Latn", "MD", "Cyrl")); + scriptsByLocale.put("gay", getScriptsMap("", "Latn")); + scriptsByLocale.put("gba", getScriptsMap("", "Arab")); + scriptsByLocale.put("gbm", getScriptsMap("", "Deva")); + scriptsByLocale.put("gcr", getScriptsMap("", "Latn")); + scriptsByLocale.put("gd", getScriptsMap("", "Latn")); + scriptsByLocale.put("gez", getScriptsMap("", "Ethi")); + scriptsByLocale.put("ggn", getScriptsMap("", "")); + scriptsByLocale.put("gil", getScriptsMap("", "Latn")); + scriptsByLocale.put("gjk", getScriptsMap("", "")); + scriptsByLocale.put("gju", getScriptsMap("", "")); + scriptsByLocale.put("gl", getScriptsMap("", "Latn")); + scriptsByLocale.put("gld", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("glk", getScriptsMap("", "")); + scriptsByLocale.put("gn", getScriptsMap("", "Latn")); + scriptsByLocale.put("gon", getScriptsMap("", "Telu")); + scriptsByLocale.put("gor", getScriptsMap("", "Latn")); + scriptsByLocale.put("gos", getScriptsMap("", "")); + scriptsByLocale.put("got", getScriptsMap("", "Goth")); + scriptsByLocale.put("grb", getScriptsMap("", "Latn")); + scriptsByLocale.put("grc", getScriptsMap("", "Cprt")); + scriptsByLocale.put("grt", getScriptsMap("", "Beng")); + scriptsByLocale.put("gsw", getScriptsMap("", "Latn")); + scriptsByLocale.put("gu", getScriptsMap("", "Gujr")); + scriptsByLocale.put("gub", getScriptsMap("", "")); + scriptsByLocale.put("guz", getScriptsMap("", "Latn")); + scriptsByLocale.put("gv", getScriptsMap("", "Latn")); + scriptsByLocale.put("gvr", getScriptsMap("", "")); + scriptsByLocale.put("gwi", getScriptsMap("", "Latn")); + scriptsByLocale.put("ha", getScriptsMap("", "Arab", "NE", "Latn", "GH", "Latn")); + scriptsByLocale.put("hai", getScriptsMap("", "Latn")); + scriptsByLocale.put("haw", getScriptsMap("", "Latn")); + scriptsByLocale.put("haz", getScriptsMap("", "")); + scriptsByLocale.put("he", getScriptsMap("", "Hebr")); + scriptsByLocale.put("hi", getScriptsMap("", "Deva")); + scriptsByLocale.put("hil", getScriptsMap("", "Latn")); + scriptsByLocale.put("hit", getScriptsMap("", "Xsux")); + scriptsByLocale.put("hmn", getScriptsMap("", "Latn")); + scriptsByLocale.put("hnd", getScriptsMap("", "")); + scriptsByLocale.put("hne", getScriptsMap("", "Deva")); + scriptsByLocale.put("hnn", getScriptsMap("", "Latn")); + scriptsByLocale.put("hno", getScriptsMap("", "")); + scriptsByLocale.put("ho", getScriptsMap("", "Latn")); + scriptsByLocale.put("hoc", getScriptsMap("", "Deva")); + scriptsByLocale.put("hoj", getScriptsMap("", "Deva")); + scriptsByLocale.put("hop", getScriptsMap("", "Latn")); + scriptsByLocale.put("hr", getScriptsMap("", "Latn")); + scriptsByLocale.put("hsb", getScriptsMap("", "Latn")); + scriptsByLocale.put("ht", getScriptsMap("", "Latn")); + scriptsByLocale.put("hu", getScriptsMap("", "Latn")); + scriptsByLocale.put("hup", getScriptsMap("", "Latn")); + scriptsByLocale.put("hy", getScriptsMap("", "Armn")); + scriptsByLocale.put("hz", getScriptsMap("", "Latn")); + scriptsByLocale.put("ia", getScriptsMap("", "Latn")); + scriptsByLocale.put("iba", getScriptsMap("", "Latn")); + scriptsByLocale.put("ibb", getScriptsMap("", "Latn")); + scriptsByLocale.put("id", getScriptsMap("", "Latn")); + scriptsByLocale.put("ig", getScriptsMap("", "Latn")); + scriptsByLocale.put("ii", getScriptsMap("", "Yiii", "CN", "Latn")); + scriptsByLocale.put("ik", getScriptsMap("", "Latn")); + scriptsByLocale.put("ikt", getScriptsMap("", "")); + scriptsByLocale.put("ilo", getScriptsMap("", "Latn")); + scriptsByLocale.put("inh", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("is", getScriptsMap("", "Latn")); + scriptsByLocale.put("it", getScriptsMap("", "Latn")); + scriptsByLocale.put("iu", getScriptsMap("", "Cans", "CA", "Latn")); + scriptsByLocale.put("ja", getScriptsMap("", "Jpan")); + scriptsByLocale.put("jmc", getScriptsMap("", "Latn")); + scriptsByLocale.put("jml", getScriptsMap("", "")); + scriptsByLocale.put("jpr", getScriptsMap("", "Hebr")); + scriptsByLocale.put("jrb", getScriptsMap("", "Hebr")); + scriptsByLocale.put("jv", getScriptsMap("", "Latn", "ID", "Java")); + scriptsByLocale.put("ka", getScriptsMap("", "Geor")); + scriptsByLocale.put("kaa", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kab", getScriptsMap("", "Latn")); + scriptsByLocale.put("kac", getScriptsMap("", "Latn")); + scriptsByLocale.put("kaj", getScriptsMap("", "Latn")); + scriptsByLocale.put("kam", getScriptsMap("", "Latn")); + scriptsByLocale.put("kao", getScriptsMap("", "")); + scriptsByLocale.put("kbd", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kca", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kcg", getScriptsMap("", "Latn")); + scriptsByLocale.put("kck", getScriptsMap("", "")); + scriptsByLocale.put("kde", getScriptsMap("", "Latn")); + scriptsByLocale.put("kdt", getScriptsMap("", "Thai")); + scriptsByLocale.put("kea", getScriptsMap("", "Latn")); + scriptsByLocale.put("kfo", getScriptsMap("", "Latn")); + scriptsByLocale.put("kfr", getScriptsMap("", "Deva")); + scriptsByLocale.put("kfy", getScriptsMap("", "")); + scriptsByLocale.put("kg", getScriptsMap("", "Latn")); + scriptsByLocale.put("kge", getScriptsMap("", "")); + scriptsByLocale.put("kgp", getScriptsMap("", "")); + scriptsByLocale.put("kha", getScriptsMap("", "Latn", "IN", "Beng")); + scriptsByLocale.put("khb", getScriptsMap("", "Talu")); + scriptsByLocale.put("khn", getScriptsMap("", "")); + scriptsByLocale.put("khq", getScriptsMap("", "Latn")); + scriptsByLocale.put("kht", getScriptsMap("", "Mymr")); + scriptsByLocale.put("khw", getScriptsMap("", "")); + scriptsByLocale.put("ki", getScriptsMap("", "Latn")); + scriptsByLocale.put("kj", getScriptsMap("", "Latn")); + scriptsByLocale.put("kjg", getScriptsMap("", "")); + scriptsByLocale.put("kjh", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kk", getScriptsMap("", "Arab", "KZ", "Cyrl", "TR", "Cyrl")); + scriptsByLocale.put("kkj", getScriptsMap("", "")); + scriptsByLocale.put("kl", getScriptsMap("", "Latn")); + scriptsByLocale.put("kln", getScriptsMap("", "Latn")); + scriptsByLocale.put("km", getScriptsMap("", "Khmr")); + scriptsByLocale.put("kmb", getScriptsMap("", "Latn")); + scriptsByLocale.put("kn", getScriptsMap("", "Knda")); + scriptsByLocale.put("ko", getScriptsMap("", "Kore")); + scriptsByLocale.put("koi", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kok", getScriptsMap("", "Deva")); + scriptsByLocale.put("kos", getScriptsMap("", "Latn")); + scriptsByLocale.put("kpe", getScriptsMap("", "Latn")); + scriptsByLocale.put("kpy", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kr", getScriptsMap("", "Latn")); + scriptsByLocale.put("krc", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kri", getScriptsMap("", "Latn")); + scriptsByLocale.put("krl", getScriptsMap("", "Latn")); + scriptsByLocale.put("kru", getScriptsMap("", "Deva")); + scriptsByLocale.put("ks", getScriptsMap("", "Arab")); + scriptsByLocale.put("ksb", getScriptsMap("", "Latn")); + scriptsByLocale.put("ksf", getScriptsMap("", "Latn")); + scriptsByLocale.put("ksh", getScriptsMap("", "Latn")); + scriptsByLocale.put("ku", getScriptsMap("", "Latn", "LB", "Arab")); + scriptsByLocale.put("kum", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kut", getScriptsMap("", "Latn")); + scriptsByLocale.put("kv", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("kvr", getScriptsMap("", "")); + scriptsByLocale.put("kvx", getScriptsMap("", "")); + scriptsByLocale.put("kw", getScriptsMap("", "Latn")); + scriptsByLocale.put("kxm", getScriptsMap("", "")); + scriptsByLocale.put("kxp", getScriptsMap("", "")); + scriptsByLocale.put("ky", getScriptsMap("", "Cyrl", "CN", "Arab", "TR", "Latn")); + scriptsByLocale.put("kyu", getScriptsMap("", "Kali")); + scriptsByLocale.put("la", getScriptsMap("", "Latn")); + scriptsByLocale.put("lad", getScriptsMap("", "Hebr")); + scriptsByLocale.put("lag", getScriptsMap("", "Latn")); + scriptsByLocale.put("lah", getScriptsMap("", "Arab")); + scriptsByLocale.put("laj", getScriptsMap("", "")); + scriptsByLocale.put("lam", getScriptsMap("", "Latn")); + scriptsByLocale.put("lb", getScriptsMap("", "Latn")); + scriptsByLocale.put("lbe", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("lbw", getScriptsMap("", "")); + scriptsByLocale.put("lcp", getScriptsMap("", "Thai")); + scriptsByLocale.put("lep", getScriptsMap("", "Lepc")); + scriptsByLocale.put("lez", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("lg", getScriptsMap("", "Latn")); + scriptsByLocale.put("li", getScriptsMap("", "Latn")); + scriptsByLocale.put("lif", getScriptsMap("", "Deva")); + scriptsByLocale.put("lis", getScriptsMap("", "Lisu")); + scriptsByLocale.put("ljp", getScriptsMap("", "")); + scriptsByLocale.put("lki", getScriptsMap("", "Arab")); + scriptsByLocale.put("lkt", getScriptsMap("", "")); + scriptsByLocale.put("lmn", getScriptsMap("", "Telu")); + scriptsByLocale.put("lmo", getScriptsMap("", "")); + scriptsByLocale.put("ln", getScriptsMap("", "Latn")); + scriptsByLocale.put("lo", getScriptsMap("", "Laoo")); + scriptsByLocale.put("lol", getScriptsMap("", "Latn")); + scriptsByLocale.put("loz", getScriptsMap("", "Latn")); + scriptsByLocale.put("lrc", getScriptsMap("", "")); + scriptsByLocale.put("lt", getScriptsMap("", "Latn")); + scriptsByLocale.put("lu", getScriptsMap("", "Latn")); + scriptsByLocale.put("lua", getScriptsMap("", "Latn")); + scriptsByLocale.put("lui", getScriptsMap("", "Latn")); + scriptsByLocale.put("lun", getScriptsMap("", "Latn")); + scriptsByLocale.put("luo", getScriptsMap("", "Latn")); + scriptsByLocale.put("lus", getScriptsMap("", "Beng")); + scriptsByLocale.put("lut", getScriptsMap("", "Latn")); + scriptsByLocale.put("luy", getScriptsMap("", "Latn")); + scriptsByLocale.put("luz", getScriptsMap("", "")); + scriptsByLocale.put("lv", getScriptsMap("", "Latn")); + scriptsByLocale.put("lwl", getScriptsMap("", "Thai")); + scriptsByLocale.put("mad", getScriptsMap("", "Latn")); + scriptsByLocale.put("maf", getScriptsMap("", "")); + scriptsByLocale.put("mag", getScriptsMap("", "Deva")); + scriptsByLocale.put("mai", getScriptsMap("", "Deva")); + scriptsByLocale.put("mak", getScriptsMap("", "Latn", "ID", "Bugi")); + scriptsByLocale.put("man", getScriptsMap("", "Latn", "GN", "Nkoo")); + scriptsByLocale.put("mas", getScriptsMap("", "Latn")); + scriptsByLocale.put("maz", getScriptsMap("", "")); + scriptsByLocale.put("mdf", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("mdh", getScriptsMap("", "Latn")); + scriptsByLocale.put("mdr", getScriptsMap("", "Latn")); + scriptsByLocale.put("mdt", getScriptsMap("", "")); + scriptsByLocale.put("men", getScriptsMap("", "Latn")); + scriptsByLocale.put("mer", getScriptsMap("", "Latn")); + scriptsByLocale.put("mfa", getScriptsMap("", "")); + scriptsByLocale.put("mfe", getScriptsMap("", "Latn")); + scriptsByLocale.put("mg", getScriptsMap("", "Latn")); + scriptsByLocale.put("mgh", getScriptsMap("", "Latn")); + scriptsByLocale.put("mgp", getScriptsMap("", "")); + scriptsByLocale.put("mgy", getScriptsMap("", "")); + scriptsByLocale.put("mh", getScriptsMap("", "Latn")); + scriptsByLocale.put("mi", getScriptsMap("", "Latn")); + scriptsByLocale.put("mic", getScriptsMap("", "Latn")); + scriptsByLocale.put("min", getScriptsMap("", "Latn")); + scriptsByLocale.put("mk", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ml", getScriptsMap("", "Mlym")); + scriptsByLocale.put("mn", getScriptsMap("", "Cyrl", "CN", "Mong")); + scriptsByLocale.put("mnc", getScriptsMap("", "Mong")); + scriptsByLocale.put("mni", getScriptsMap("", "Beng", "IN", "Mtei")); + scriptsByLocale.put("mns", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("mnw", getScriptsMap("", "Mymr")); + scriptsByLocale.put("moe", getScriptsMap("", "")); + scriptsByLocale.put("moh", getScriptsMap("", "Latn")); + scriptsByLocale.put("mos", getScriptsMap("", "Latn")); + scriptsByLocale.put("mr", getScriptsMap("", "Deva")); + scriptsByLocale.put("mrd", getScriptsMap("", "")); + scriptsByLocale.put("mrj", getScriptsMap("", "")); + scriptsByLocale.put("ms", getScriptsMap("", "Arab", "MY", "Latn", "SG", "Latn")); + scriptsByLocale.put("mt", getScriptsMap("", "Latn")); + scriptsByLocale.put("mtr", getScriptsMap("", "")); + scriptsByLocale.put("mua", getScriptsMap("", "Latn")); + scriptsByLocale.put("mus", getScriptsMap("", "Latn")); + scriptsByLocale.put("mvy", getScriptsMap("", "")); + scriptsByLocale.put("mwk", getScriptsMap("", "")); + scriptsByLocale.put("mwl", getScriptsMap("", "Latn")); + scriptsByLocale.put("mwr", getScriptsMap("", "Deva")); + scriptsByLocale.put("mxc", getScriptsMap("", "")); + scriptsByLocale.put("my", getScriptsMap("", "Mymr")); + scriptsByLocale.put("myv", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("myx", getScriptsMap("", "")); + scriptsByLocale.put("myz", getScriptsMap("", "Mand")); + scriptsByLocale.put("na", getScriptsMap("", "Latn")); + scriptsByLocale.put("nap", getScriptsMap("", "Latn")); + scriptsByLocale.put("naq", getScriptsMap("", "Latn")); + scriptsByLocale.put("nb", getScriptsMap("", "Latn")); + scriptsByLocale.put("nbf", getScriptsMap("", "")); + scriptsByLocale.put("nch", getScriptsMap("", "")); + scriptsByLocale.put("nd", getScriptsMap("", "Latn")); + scriptsByLocale.put("ndc", getScriptsMap("", "")); + scriptsByLocale.put("nds", getScriptsMap("", "Latn")); + scriptsByLocale.put("ne", getScriptsMap("", "Deva")); + scriptsByLocale.put("new", getScriptsMap("", "Deva")); + scriptsByLocale.put("ng", getScriptsMap("", "Latn")); + scriptsByLocale.put("ngl", getScriptsMap("", "")); + scriptsByLocale.put("nhe", getScriptsMap("", "")); + scriptsByLocale.put("nhw", getScriptsMap("", "")); + scriptsByLocale.put("nia", getScriptsMap("", "Latn")); + scriptsByLocale.put("nij", getScriptsMap("", "")); + scriptsByLocale.put("niu", getScriptsMap("", "Latn")); + scriptsByLocale.put("nl", getScriptsMap("", "Latn")); + scriptsByLocale.put("nmg", getScriptsMap("", "Latn")); + scriptsByLocale.put("nn", getScriptsMap("", "Latn")); + scriptsByLocale.put("nnh", getScriptsMap("", "")); + scriptsByLocale.put("nod", getScriptsMap("", "Lana")); + scriptsByLocale.put("noe", getScriptsMap("", "")); + scriptsByLocale.put("nog", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("nqo", getScriptsMap("", "Nkoo")); + scriptsByLocale.put("nr", getScriptsMap("", "Latn")); + scriptsByLocale.put("nsk", getScriptsMap("", "")); + scriptsByLocale.put("nso", getScriptsMap("", "Latn")); + scriptsByLocale.put("nus", getScriptsMap("", "Latn")); + scriptsByLocale.put("nv", getScriptsMap("", "Latn")); + scriptsByLocale.put("ny", getScriptsMap("", "Latn")); + scriptsByLocale.put("nym", getScriptsMap("", "Latn")); + scriptsByLocale.put("nyn", getScriptsMap("", "Latn")); + scriptsByLocale.put("nyo", getScriptsMap("", "Latn")); + scriptsByLocale.put("nzi", getScriptsMap("", "Latn")); + scriptsByLocale.put("oc", getScriptsMap("", "Latn")); + scriptsByLocale.put("oj", getScriptsMap("", "Cans")); + scriptsByLocale.put("om", getScriptsMap("", "Latn", "ET", "Ethi")); + scriptsByLocale.put("or", getScriptsMap("", "Orya")); + scriptsByLocale.put("os", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("osa", getScriptsMap("", "Latn")); + scriptsByLocale.put("osc", getScriptsMap("", "Ital")); + scriptsByLocale.put("otk", getScriptsMap("", "Orkh")); + scriptsByLocale.put("pa", getScriptsMap("", "Guru", "PK", "Arab")); + scriptsByLocale.put("pag", getScriptsMap("", "Latn")); + scriptsByLocale.put("pal", getScriptsMap("", "Phli")); + scriptsByLocale.put("pam", getScriptsMap("", "Latn")); + scriptsByLocale.put("pap", getScriptsMap("", "Latn")); + scriptsByLocale.put("pau", getScriptsMap("", "Latn")); + scriptsByLocale.put("peo", getScriptsMap("", "Xpeo")); + scriptsByLocale.put("phn", getScriptsMap("", "Phnx")); + scriptsByLocale.put("pi", getScriptsMap("", "Deva")); + scriptsByLocale.put("pko", getScriptsMap("", "")); + scriptsByLocale.put("pl", getScriptsMap("", "Latn")); + scriptsByLocale.put("pon", getScriptsMap("", "Latn")); + scriptsByLocale.put("pra", getScriptsMap("", "Brah")); + scriptsByLocale.put("prd", getScriptsMap("", "Arab")); + scriptsByLocale.put("prg", getScriptsMap("", "Latn")); + scriptsByLocale.put("prs", getScriptsMap("", "Arab")); + scriptsByLocale.put("ps", getScriptsMap("", "Arab")); + scriptsByLocale.put("pt", getScriptsMap("", "Latn")); + scriptsByLocale.put("puu", getScriptsMap("", "")); + scriptsByLocale.put("qu", getScriptsMap("", "Latn")); + scriptsByLocale.put("raj", getScriptsMap("", "Latn")); + scriptsByLocale.put("rap", getScriptsMap("", "Latn")); + scriptsByLocale.put("rar", getScriptsMap("", "Latn")); + scriptsByLocale.put("rcf", getScriptsMap("", "Latn")); + scriptsByLocale.put("rej", getScriptsMap("", "Latn", "ID", "Rjng")); + scriptsByLocale.put("ria", getScriptsMap("", "")); + scriptsByLocale.put("rif", getScriptsMap("", "")); + scriptsByLocale.put("rjs", getScriptsMap("", "Deva")); + scriptsByLocale.put("rkt", getScriptsMap("", "Beng")); + scriptsByLocale.put("rm", getScriptsMap("", "Latn")); + scriptsByLocale.put("rmf", getScriptsMap("", "")); + scriptsByLocale.put("rmo", getScriptsMap("", "")); + scriptsByLocale.put("rmt", getScriptsMap("", "")); + scriptsByLocale.put("rn", getScriptsMap("", "Latn")); + scriptsByLocale.put("rng", getScriptsMap("", "")); + scriptsByLocale.put("ro", getScriptsMap("", "Latn", "RS", "Cyrl")); + scriptsByLocale.put("rob", getScriptsMap("", "")); + scriptsByLocale.put("rof", getScriptsMap("", "Latn")); + scriptsByLocale.put("rom", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ru", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("rue", getScriptsMap("", "")); + scriptsByLocale.put("rup", getScriptsMap("", "Latn")); + scriptsByLocale.put("rw", getScriptsMap("", "Latn")); + scriptsByLocale.put("rwk", getScriptsMap("", "Latn")); + scriptsByLocale.put("ryu", getScriptsMap("", "")); + scriptsByLocale.put("sa", getScriptsMap("", "Deva")); + scriptsByLocale.put("sad", getScriptsMap("", "Latn")); + scriptsByLocale.put("saf", getScriptsMap("", "Latn")); + scriptsByLocale.put("sah", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("sam", getScriptsMap("", "Hebr")); + scriptsByLocale.put("saq", getScriptsMap("", "Latn")); + scriptsByLocale.put("sas", getScriptsMap("", "Latn")); + scriptsByLocale.put("sat", getScriptsMap("", "Latn")); + scriptsByLocale.put("saz", getScriptsMap("", "Saur")); + scriptsByLocale.put("sbp", getScriptsMap("", "Latn")); + scriptsByLocale.put("sc", getScriptsMap("", "Latn")); + scriptsByLocale.put("sck", getScriptsMap("", "")); + scriptsByLocale.put("scn", getScriptsMap("", "Latn")); + scriptsByLocale.put("sco", getScriptsMap("", "Latn")); + scriptsByLocale.put("scs", getScriptsMap("", "")); + scriptsByLocale.put("sd", getScriptsMap("", "Arab", "IN", "Deva")); + scriptsByLocale.put("sdh", getScriptsMap("", "Arab")); + scriptsByLocale.put("se", getScriptsMap("", "Latn", "NO", "Cyrl")); + scriptsByLocale.put("see", getScriptsMap("", "Latn")); + scriptsByLocale.put("sef", getScriptsMap("", "")); + scriptsByLocale.put("seh", getScriptsMap("", "Latn")); + scriptsByLocale.put("sel", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ses", getScriptsMap("", "Latn")); + scriptsByLocale.put("sg", getScriptsMap("", "Latn")); + scriptsByLocale.put("sga", getScriptsMap("", "Latn")); + scriptsByLocale.put("shi", getScriptsMap("", "Tfng")); + scriptsByLocale.put("shn", getScriptsMap("", "Mymr")); + scriptsByLocale.put("si", getScriptsMap("", "Sinh")); + scriptsByLocale.put("sid", getScriptsMap("", "Latn")); + scriptsByLocale.put("sk", getScriptsMap("", "Latn")); + scriptsByLocale.put("skr", getScriptsMap("", "")); + scriptsByLocale.put("sl", getScriptsMap("", "Latn")); + scriptsByLocale.put("sm", getScriptsMap("", "Latn")); + scriptsByLocale.put("sma", getScriptsMap("", "Latn")); + scriptsByLocale.put("smi", getScriptsMap("", "Latn")); + scriptsByLocale.put("smj", getScriptsMap("", "Latn")); + scriptsByLocale.put("smn", getScriptsMap("", "Latn")); + scriptsByLocale.put("sms", getScriptsMap("", "Latn")); + scriptsByLocale.put("sn", getScriptsMap("", "Latn")); + scriptsByLocale.put("snk", getScriptsMap("", "Latn")); + scriptsByLocale.put("so", getScriptsMap("", "Latn")); + scriptsByLocale.put("son", getScriptsMap("", "Latn")); + scriptsByLocale.put("sou", getScriptsMap("", "")); + scriptsByLocale.put("sq", getScriptsMap("", "Latn")); + scriptsByLocale.put("sr", getScriptsMap("", "Latn")); + scriptsByLocale.put("srn", getScriptsMap("", "Latn")); + scriptsByLocale.put("srr", getScriptsMap("", "Latn")); + scriptsByLocale.put("srx", getScriptsMap("", "")); + scriptsByLocale.put("ss", getScriptsMap("", "Latn")); + scriptsByLocale.put("ssy", getScriptsMap("", "Latn")); + scriptsByLocale.put("st", getScriptsMap("", "Latn")); + scriptsByLocale.put("su", getScriptsMap("", "Latn")); + scriptsByLocale.put("suk", getScriptsMap("", "Latn")); + scriptsByLocale.put("sus", getScriptsMap("", "Latn", "GN", "Arab")); + scriptsByLocale.put("sv", getScriptsMap("", "Latn")); + scriptsByLocale.put("sw", getScriptsMap("", "Latn")); + scriptsByLocale.put("swb", getScriptsMap("", "Arab", "YT", "Latn")); + scriptsByLocale.put("swc", getScriptsMap("", "Latn")); + scriptsByLocale.put("swv", getScriptsMap("", "")); + scriptsByLocale.put("sxn", getScriptsMap("", "")); + scriptsByLocale.put("syi", getScriptsMap("", "")); + scriptsByLocale.put("syl", getScriptsMap("", "Beng", "BD", "Sylo")); + scriptsByLocale.put("syr", getScriptsMap("", "Syrc")); + scriptsByLocale.put("ta", getScriptsMap("", "Taml")); + scriptsByLocale.put("tab", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("taj", getScriptsMap("", "")); + scriptsByLocale.put("tbw", getScriptsMap("", "Latn")); + scriptsByLocale.put("tcy", getScriptsMap("", "Knda")); + scriptsByLocale.put("tdd", getScriptsMap("", "Tale")); + scriptsByLocale.put("tdg", getScriptsMap("", "")); + scriptsByLocale.put("tdh", getScriptsMap("", "")); + scriptsByLocale.put("te", getScriptsMap("", "Telu")); + scriptsByLocale.put("tem", getScriptsMap("", "Latn")); + scriptsByLocale.put("teo", getScriptsMap("", "Latn")); + scriptsByLocale.put("ter", getScriptsMap("", "Latn")); + scriptsByLocale.put("tet", getScriptsMap("", "Latn")); + scriptsByLocale.put("tg", getScriptsMap("", "Cyrl", "PK", "Arab")); + scriptsByLocale.put("th", getScriptsMap("", "Thai")); + scriptsByLocale.put("thl", getScriptsMap("", "")); + scriptsByLocale.put("thq", getScriptsMap("", "")); + scriptsByLocale.put("thr", getScriptsMap("", "")); + scriptsByLocale.put("ti", getScriptsMap("", "Ethi")); + scriptsByLocale.put("tig", getScriptsMap("", "Ethi")); + scriptsByLocale.put("tiv", getScriptsMap("", "Latn")); + scriptsByLocale.put("tk", getScriptsMap("", "Latn")); + scriptsByLocale.put("tkl", getScriptsMap("", "Latn")); + scriptsByLocale.put("tkt", getScriptsMap("", "")); + scriptsByLocale.put("tli", getScriptsMap("", "Latn")); + scriptsByLocale.put("tmh", getScriptsMap("", "Latn")); + scriptsByLocale.put("tn", getScriptsMap("", "Latn")); + scriptsByLocale.put("to", getScriptsMap("", "Latn")); + scriptsByLocale.put("tog", getScriptsMap("", "Latn")); + scriptsByLocale.put("tpi", getScriptsMap("", "Latn")); + scriptsByLocale.put("tr", getScriptsMap("", "Latn", "DE", "Arab", "MK", "Arab")); + scriptsByLocale.put("tru", getScriptsMap("", "Latn")); + scriptsByLocale.put("trv", getScriptsMap("", "Latn")); + scriptsByLocale.put("ts", getScriptsMap("", "Latn")); + scriptsByLocale.put("tsf", getScriptsMap("", "")); + scriptsByLocale.put("tsg", getScriptsMap("", "Latn")); + scriptsByLocale.put("tsi", getScriptsMap("", "Latn")); + scriptsByLocale.put("tsj", getScriptsMap("", "")); + scriptsByLocale.put("tt", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("ttj", getScriptsMap("", "")); + scriptsByLocale.put("tts", getScriptsMap("", "Thai")); + scriptsByLocale.put("tum", getScriptsMap("", "Latn")); + scriptsByLocale.put("tut", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("tvl", getScriptsMap("", "Latn")); + scriptsByLocale.put("twq", getScriptsMap("", "Latn")); + scriptsByLocale.put("ty", getScriptsMap("", "Latn")); + scriptsByLocale.put("tyv", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("tzm", getScriptsMap("", "Latn")); + scriptsByLocale.put("ude", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("udm", getScriptsMap("", "Cyrl", "RU", "Latn")); + scriptsByLocale.put("ug", getScriptsMap("", "Arab", "KZ", "Cyrl", "MN", "Cyrl")); + scriptsByLocale.put("uga", getScriptsMap("", "Ugar")); + scriptsByLocale.put("uk", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("uli", getScriptsMap("", "Latn")); + scriptsByLocale.put("umb", getScriptsMap("", "Latn")); + scriptsByLocale.put("und", getScriptsMap("", "")); + scriptsByLocale.put("unr", getScriptsMap("", "Beng", "NP", "Deva")); + scriptsByLocale.put("unx", getScriptsMap("", "Beng")); + scriptsByLocale.put("ur", getScriptsMap("", "Arab")); + scriptsByLocale.put("uz", getScriptsMap("", "Latn", "AF", "Arab", "CN", "Cyrl")); + scriptsByLocale.put("vai", getScriptsMap("", "Vaii")); + scriptsByLocale.put("ve", getScriptsMap("", "Latn")); + scriptsByLocale.put("vi", getScriptsMap("", "Latn", "US", "Hani")); + scriptsByLocale.put("vic", getScriptsMap("", "")); + scriptsByLocale.put("vmw", getScriptsMap("", "")); + scriptsByLocale.put("vo", getScriptsMap("", "Latn")); + scriptsByLocale.put("vot", getScriptsMap("", "Latn")); + scriptsByLocale.put("vun", getScriptsMap("", "Latn")); + scriptsByLocale.put("wa", getScriptsMap("", "Latn")); + scriptsByLocale.put("wae", getScriptsMap("", "Latn")); + scriptsByLocale.put("wak", getScriptsMap("", "Latn")); + scriptsByLocale.put("wal", getScriptsMap("", "Ethi")); + scriptsByLocale.put("war", getScriptsMap("", "Latn")); + scriptsByLocale.put("was", getScriptsMap("", "Latn")); + scriptsByLocale.put("wbq", getScriptsMap("", "")); + scriptsByLocale.put("wbr", getScriptsMap("", "")); + scriptsByLocale.put("wls", getScriptsMap("", "")); + scriptsByLocale.put("wo", getScriptsMap("", "Latn")); + scriptsByLocale.put("wtm", getScriptsMap("", "")); + scriptsByLocale.put("xal", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("xav", getScriptsMap("", "")); + scriptsByLocale.put("xcr", getScriptsMap("", "Cari")); + scriptsByLocale.put("xh", getScriptsMap("", "Latn")); + scriptsByLocale.put("xnr", getScriptsMap("", "")); + scriptsByLocale.put("xog", getScriptsMap("", "Latn")); + scriptsByLocale.put("xpr", getScriptsMap("", "Prti")); + scriptsByLocale.put("xsa", getScriptsMap("", "Sarb")); + scriptsByLocale.put("xsr", getScriptsMap("", "Deva")); + scriptsByLocale.put("xum", getScriptsMap("", "Ital")); + scriptsByLocale.put("yao", getScriptsMap("", "Latn")); + scriptsByLocale.put("yap", getScriptsMap("", "Latn")); + scriptsByLocale.put("yav", getScriptsMap("", "Latn")); + scriptsByLocale.put("ybb", getScriptsMap("", "")); + scriptsByLocale.put("yi", getScriptsMap("", "Hebr")); + scriptsByLocale.put("yo", getScriptsMap("", "Latn")); + scriptsByLocale.put("yrk", getScriptsMap("", "Cyrl")); + scriptsByLocale.put("yua", getScriptsMap("", "")); + scriptsByLocale.put("yue", getScriptsMap("", "Hans")); + scriptsByLocale.put("za", getScriptsMap("", "Latn", "CN", "Hans")); + scriptsByLocale.put("zap", getScriptsMap("", "Latn")); + scriptsByLocale.put("zdj", getScriptsMap("", "")); + scriptsByLocale.put("zea", getScriptsMap("", "")); + scriptsByLocale.put("zen", getScriptsMap("", "Tfng")); + scriptsByLocale.put("zh", getScriptsMap("", "Hant", "CN", "Hans", "HK", "Hans", "MO", "Hans", "SG", "Hans", "MN", "Hans")); + scriptsByLocale.put("zmi", getScriptsMap("", "")); + scriptsByLocale.put("zu", getScriptsMap("", "Latn")); + scriptsByLocale.put("zun", getScriptsMap("", "Latn")); + scriptsByLocale.put("zza", getScriptsMap("", "Arab")); + } + + /** + * Gets the script (writing type) for the given locale. For example, if a US citizen uses German Locale, + * and calls this method with Locale.getDefault(), the result would be "Runr" + * + * @param locale + * @return + */ + public static String getScript(Locale locale) { + String localeString = locale.toString(); + String language = ""; + String country = ""; + if (localeString.contains("_")) { + String[] split = localeString.split("_"); + language = split[0]; + country = split[1]; + } else language = localeString; + + Map scripts = scriptsByLocale.get(language); + String script = scripts.get(country); + return script == null ? scripts.get("") : script; + + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleUtility.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleUtility.java new file mode 100644 index 0000000..dc92941 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/LocaleUtility.java @@ -0,0 +1,57 @@ +package com.liskovsoft.leankeyboard.utils; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build.VERSION; +import android.util.Log; + +import java.util.Locale; + +public class LocaleUtility extends LocaleScript { + private static final String TAG = LocaleUtility.class.getSimpleName(); + + public static Locale getSystemLocale(Context context) { + return getSystemLocale(context.getResources().getConfiguration()); + } + + public static void setSystemLocale(Context context, Locale locale) { + setSystemLocale(context.getResources().getConfiguration(), locale); + } + + @SuppressWarnings("deprecation") + public static void setSystemLocale(Configuration config, Locale locale) { + if (VERSION.SDK_INT < 24) { + config.locale = locale; + } else { + config.setLocale(locale); + } + } + + @SuppressWarnings("deprecation") + public static Locale getSystemLocale(Configuration config) { + if (VERSION.SDK_INT < 24) { + return config.locale; + } else { + return config.getLocales().get(0); + } + } + + /** + * Modern Solution + */ + @SuppressWarnings("deprecation") + public static void forceLocaleOld(Context ctx, Locale locale) { + Locale.setDefault(locale); + Configuration config = ctx.getResources().getConfiguration(); + LocaleUtility.setSystemLocale(config, locale); + ctx.getResources().updateConfiguration(config, + ctx.getResources().getDisplayMetrics()); + } + + public static void switchRuLocale(Context ctx) { + Log.d(TAG, "Trying to switch locale back and forward"); + Locale savedLocale = Locale.getDefault(); + LocaleUtility.forceLocaleOld(ctx, new Locale("ru")); + LocaleUtility.forceLocaleOld(ctx, savedLocale); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/TextDrawable.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/TextDrawable.java new file mode 100644 index 0000000..51f1c53 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/utils/TextDrawable.java @@ -0,0 +1,510 @@ +/** + * Example usage: https://github.com/devunwired/textdrawable/blob/master/sample/src/main/java/com/example/textdrawable/MyActivity.java + */ + +/** + * Copyright (c) 2012 Wireless Designs, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.liskovsoft.leankeyboard.utils; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.TypedValue; +import androidx.annotation.NonNull; + +/** + * A Drawable object that draws text. + * A TextDrawable accepts most of the same parameters that can be applied to + * {@link android.widget.TextView} for displaying and formatting text. + * + * Optionally, a {@link Path} may be supplied on which to draw the text. + * + * A TextDrawable has an intrinsic size equal to that required to draw all + * the text it has been supplied, when possible. In cases where a {@link Path} + * has been supplied, the caller must explicitly call + * {@link #setBounds(Rect) setBounds()} to provide the Drawable + * size based on the Path constraints. + */ +public class TextDrawable extends Drawable { + + /* Platform XML constants for typeface */ + private static final int SANS = 1; + private static final int SERIF = 2; + private static final int MONOSPACE = 3; + + /* Resources for scaling values to the given device */ + private Resources mResources; + /* Paint to hold most drawing primitives for the text */ + private TextPaint mTextPaint; + /* Layout is used to measure and draw the text */ + private StaticLayout mTextLayout; + /* Alignment of the text inside its bounds */ + private Layout.Alignment mTextAlignment = Layout.Alignment.ALIGN_NORMAL; + /* Optional path on which to draw the text */ + private Path mTextPath; + /* Stateful text color list */ + private ColorStateList mTextColors; + /* Container for the bounds to be reported to widgets */ + private Rect mTextBounds; + /* Text string to draw */ + private CharSequence mText = ""; + private final Drawable mDrawable; + + /* Attribute lists to pull default values from the current theme */ + private static final int[] themeAttributes = { + android.R.attr.textAppearance + }; + private static final int[] appearanceAttributes = { + android.R.attr.textSize, + android.R.attr.typeface, + android.R.attr.textStyle, + android.R.attr.textColor + }; + private float mTextSizeFactor; + + public TextDrawable(Context context) { + this(context, null); + } + + public TextDrawable(Context context, Drawable drawable) { + super(); + mDrawable = drawable; + //Used to load and scale resource items + mResources = context.getResources(); + //Definition of this drawables size + mTextBounds = new Rect(); + //Paint to use for the text + mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.density = mResources.getDisplayMetrics().density; + mTextPaint.setDither(true); + + if (mDrawable != null) { + setBounds(mDrawable.getBounds()); + } + + int textSize = 15; + ColorStateList textColor = null; + int styleIndex = -1; + int typefaceIndex = -1; + + //Set default parameters from the current theme + TypedArray a = context.getTheme().obtainStyledAttributes(themeAttributes); + int appearanceId = a.getResourceId(0, -1); + a.recycle(); + + TypedArray ap = null; + if (appearanceId != -1) { + ap = context.obtainStyledAttributes(appearanceId, appearanceAttributes); + } + if (ap != null) { + for (int i=0; i < ap.getIndexCount(); i++) { + int attr = ap.getIndex(i); + switch (attr) { + case 0: //Text Size + textSize = a.getDimensionPixelSize(attr, textSize); + break; + case 1: //Typeface + typefaceIndex = a.getInt(attr, typefaceIndex); + break; + case 2: //Text Style + styleIndex = a.getInt(attr, styleIndex); + break; + case 3: //Text Color + textColor = a.getColorStateList(attr); + break; + default: + break; + } + } + + ap.recycle(); + } + + setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); + setRawTextSize(textSize); + + Typeface tf = null; + switch (typefaceIndex) { + case SANS: + tf = Typeface.SANS_SERIF; + break; + + case SERIF: + tf = Typeface.SERIF; + break; + + case MONOSPACE: + tf = Typeface.MONOSPACE; + break; + } + + setTypeface(tf, styleIndex); + } + + /** + * Set the text that will be displayed + * @param text Text to display + */ + public void setText(CharSequence text) { + if (text == null) text = ""; + + mText = text; + + measureContent(); + } + + /** + * Return the text currently being displayed + */ + public CharSequence getText() { + return mText; + } + + /** + * Return the current text size, in pixels + */ + public float getTextSize() { + return mTextPaint.getTextSize(); + } + + /** + * Set the text size. The value will be interpreted in "sp" units + * @param size Text size value, in sp + */ + public void setTextSize(float size) { + setTextSize(TypedValue.COMPLEX_UNIT_SP, size); + } + + /** + * Set the text size, using the supplied complex units + * @param unit Units for the text size, such as dp or sp + * @param size Text size value + */ + public void setTextSize(int unit, float size) { + float dimension = TypedValue.applyDimension(unit, size, + mResources.getDisplayMetrics()); + setRawTextSize(dimension); + } + + /** + * Text size compare to canvas size + */ + public void setTextSizeFactor(float factor) { + mTextSizeFactor = factor; + } + + /* + * Set the text size, in raw pixels + */ + private void setRawTextSize(float size) { + if (size != mTextPaint.getTextSize()) { + mTextPaint.setTextSize(size); + + measureContent(); + } + } + + /** + * Return the horizontal stretch factor of the text + */ + public float getTextScaleX() { + return mTextPaint.getTextScaleX(); + } + + /** + * Set the horizontal stretch factor of the text + * @param size Text scale factor + */ + public void setTextScaleX(float size) { + if (size != mTextPaint.getTextScaleX()) { + mTextPaint.setTextScaleX(size); + measureContent(); + } + } + + /** + * Return the current text alignment setting + */ + public Layout.Alignment getTextAlign() { + return mTextAlignment; + } + + /** + * Set the text alignment. The alignment itself is based on the text layout direction. + * For LTR text NORMAL is left aligned and OPPOSITE is right aligned. + * For RTL text, those alignments are reversed. + * @param align Text alignment value. Should be set to one of: + * + * {@link Layout.Alignment#ALIGN_NORMAL}, + * {@link Layout.Alignment#ALIGN_NORMAL}, + * {@link Layout.Alignment#ALIGN_OPPOSITE}. + */ + public void setTextAlign(Layout.Alignment align) { + if (mTextAlignment != align) { + mTextAlignment = align; + measureContent(); + } + } + + /** + * Sets the typeface and style in which the text should be displayed. + * Note that not all Typeface families actually have bold and italic + * variants, so you may need to use + * {@link #setTypeface(Typeface, int)} to get the appearance + * that you actually want. + */ + public void setTypeface(Typeface tf) { + if (mTextPaint.getTypeface() != tf) { + mTextPaint.setTypeface(tf); + + measureContent(); + } + } + + /** + * Sets the typeface and style in which the text should be displayed, + * and turns on the fake bold and italic bits in the Paint if the + * Typeface that you provided does not have all the bits in the + * style that you specified. + * + */ + public void setTypeface(Typeface tf, int style) { + if (style > 0) { + if (tf == null) { + tf = Typeface.defaultFromStyle(style); + } else { + tf = Typeface.create(tf, style); + } + + setTypeface(tf); + // now compute what (if any) algorithmic styling is needed + int typefaceStyle = tf != null ? tf.getStyle() : 0; + int need = style & ~typefaceStyle; + mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); + mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); + } else { + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(0); + setTypeface(tf); + } + } + + /** + * Return the current typeface and style that the Paint + * using for display. + */ + public Typeface getTypeface() { + return mTextPaint.getTypeface(); + } + + /** + * Set a single text color for all states + * @param color Color value such as {@link Color#WHITE} or {@link Color#argb(int, int, int, int)} + */ + public void setTextColor(int color) { + setTextColor(ColorStateList.valueOf(color)); + } + + /** + * Set the text color as a state list + * @param colorStateList ColorStateList of text colors, such as inflated from an R.color resource + */ + public void setTextColor(ColorStateList colorStateList) { + mTextColors = colorStateList; + updateTextColors(getState()); + } + + /** + * Optional Path object on which to draw the text. If this is set, + * TextDrawable cannot properly measure the bounds this drawable will need. + * You must call {@link #setBounds(int, int, int, int) setBounds()} before + * applying this TextDrawable to any View. + * + * Calling this method with null will remove any Path currently attached. + */ + public void setTextPath(Path path) { + if (mTextPath != path) { + mTextPath = path; + measureContent(); + } + } + + /** + * Internal method to take measurements of the current contents and apply + * the correct bounds when possible. + */ + private void measureContent() { + //If drawing to a path, we cannot measure intrinsic bounds + //We must resly on setBounds being called externally + if (mTextPath != null) { + //Clear any previous measurement + mTextLayout = null; + mTextBounds.setEmpty(); + } else { + //Measure text bounds + double desired = Math.ceil(Layout.getDesiredWidth(mText, mTextPaint)); + + if (mDrawable != null) { + desired = mDrawable.getIntrinsicWidth(); + } + + mTextLayout = new StaticLayout(mText, mTextPaint, (int) desired, + mTextAlignment, 1.0f, 0.0f, false); + + mTextBounds.set(0, 0, mTextLayout.getWidth(), mTextLayout.getHeight()); + } + + //We may need to be redrawn + invalidateSelf(); + } + + /** + * Internal method to apply the correct text color based on the drawable's state + */ + private boolean updateTextColors(int[] stateSet) { + int newColor = mTextColors.getColorForState(stateSet, Color.WHITE); + if (mTextPaint.getColor() != newColor) { + mTextPaint.setColor(newColor); + + // fully transparent text + mTextPaint.setAlpha(1); + mTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); + + return true; + } + + return false; + } + + @Override + protected void onBoundsChange(Rect bounds) { + //Update the internal bounds in response to any external requests + mTextBounds.set(bounds); + } + + @Override + public boolean isStateful() { + /* + * The drawable's ability to represent state is based on + * the text color list set + */ + return mTextColors.isStateful(); + } + + @Override + protected boolean onStateChange(int[] state) { + //Upon state changes, grab the correct text color + return updateTextColors(state); + } + + @Override + public int getIntrinsicHeight() { + //Return the vertical bounds measured, or -1 if none + if (mTextBounds.isEmpty()) { + return -1; + } else { + return (mTextBounds.bottom - mTextBounds.top); + } + } + + @Override + public int getIntrinsicWidth() { + //Return the horizontal bounds measured, or -1 if none + if (mTextBounds.isEmpty()) { + return -1; + } else { + return (mTextBounds.right - mTextBounds.left); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + final Rect bounds = getBounds(); + + final int count = canvas.save(); + //canvas.translate(bounds.left, bounds.top); + + if (mDrawable != null) { + // scale drawable to fit canvas + Rect clipBounds = canvas.getClipBounds(); + mDrawable.setBounds(clipBounds); + + mDrawable.draw(canvas); + } + + if (mTextPath == null) { + //Allow the layout to draw the text + + // Center text vertically!! + canvas.translate((bounds.width() / 2f) - (mTextLayout.getWidth() / 2f), (bounds.height() / 2f) - ((mTextLayout.getHeight() / 2f))); + + if (mTextSizeFactor > 0) { + setTextSize(TypedValue.COMPLEX_UNIT_PX, bounds.height() * mTextSizeFactor); + } + + mTextLayout.draw(canvas); + + // Set text transparent + //canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + } else { + //Draw directly on the canvas using the supplied path + canvas.drawTextOnPath(mText.toString(), mTextPath, 0, 0, mTextPaint); + } + canvas.restoreToCount(count); + } + + @Override + public void setAlpha(int alpha) { + if (mTextPaint.getAlpha() != alpha) { + mTextPaint.setAlpha(alpha); + } + } + + @Override + public int getOpacity() { + return mTextPaint.getAlpha(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (mTextPaint.getColorFilter() != cf) { + mTextPaint.setColorFilter(cf); + } + } + +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/widgets/DialogTitle.java b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/widgets/DialogTitle.java new file mode 100644 index 0000000..708c3dd --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/leankeyboard/widgets/DialogTitle.java @@ -0,0 +1,64 @@ +// Useful links: +// https://android.googlesource.com/platform/frameworks/base/+/de47f1c358c8186ff3e14b887d5869f69b9a9d6c/core/java/com/android/internal/widget/DialogTitle.java +// com.android.internal.widget.DialogTitle: https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/alert_dialog.xml +// https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/app/AlertController.java +// : https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/res/values/attrs.xml + + +package com.liskovsoft.leankeyboard.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.text.Layout; +import android.util.AttributeSet; +import android.util.TypedValue; +import androidx.appcompat.widget.AppCompatTextView; +import com.liskovsoft.leankeykeyboard.R; + +/** + * Used by dialogs to change the font size and number of lines to try to fit + * the text to the available space. + */ +public class DialogTitle extends AppCompatTextView { + + public DialogTitle(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + public DialogTitle(Context context, AttributeSet attrs) { + super(context, attrs); + } + public DialogTitle(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + final Layout layout = getLayout(); + if (layout != null) { + final int lineCount = layout.getLineCount(); + if (lineCount > 0) { + final int ellipsisCount = layout.getEllipsisCount(lineCount - 1); + if (ellipsisCount > 0) { + setSingleLine(false); + + TypedArray a = getContext().obtainStyledAttributes(null, + R.styleable.TextAppearance, + android.R.attr.textAppearanceMedium, + android.R.style.TextAppearance_Medium); + final int textSize = a.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, + (int) (20 * getResources().getDisplayMetrics().density)); + final int textColor = a.getColor( + R.styleable.TextAppearance_textColor, 0xffffffff); + // textSize is already expressed in pixels + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + setTextColor(textColor); + setMaxLines(2); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + } + } +} 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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 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-nodpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000000000000000000000000000000000000..8526a43f45ef4863b8322ce531c70e2f05f9f259 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~dVo)e>;M1%-w^{AZu<$;!dnvL z7tFw5!OrIKW7~gE%k4j(S553^U|=-zba4!cIQ;fnv{g?$_>d@vFF^AzUCcVaI|%S_qFx{&rl7kT_SUXqh4RA zv7Gg#&@XefQyTxS66-|suS-RjTwyeep4!nYvtKEY{gAkDc&=gonlVO ziY%J+;6Q+uCm)~cq$eg0=bKcPvnC0Z&Qee^T+ZvI^3KHR{_m;fj>0LEm;RKWmUc4j z_q(&(-u$S2{?Y!g@;6}~Ra>=h!dw&Wrl<%c?bY%~*!Zz}mUqO}XWwlnFZeXQs@eNQ zvC238=?(0cw7*aB`l=pXJR?L_sifV@G^2Re=bCqc^4ZI-zC5QZtG(%F;H|3{^}Pxc zocroas}m(m{}nBeFVgNip7#31n}3b9DJdyS4D$=@C7S*|`q~W)Rt8U3KbLh*2~7YF Csm$#F literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-nodpi-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-nodpi-v4/ic_ime_clipboard.png b/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..359bb4575c5be54f1c6b053cf3d7bff863065205 GIT binary patch literal 2046 zcmaJ?Yg7~079I(21hkbZ)aoP%LN!kk$V8)rBuc<=F+7X{mXHh~m}FuSAUtkOlSmaY zAY6+ARdn$Xv@I9WVrA7>pnzTr*HS=N#nu+F6_A(Gs^Fbau>Dc)tTl7a*=v8_ew=Tw zIUE_W-PPI482|uRUMNR^uA$b~(H{NAj9lD~E}mMhP#c9LYcoV@7+^_|BpBo=L{eA) zizJ!*JK@a$fbo?@3$;T24u%*}5JXlVf=;1A(EzY{t4<{n?}N2q5-gP|nYbrcui-$M zgo)clM^Zw5B*;e8Fi0cN@M02$3baHCmBU4D&cl9LhqF$s`Pj1kqW6)Bq}(ODB`LEEXq( z&JN(R=@6YALV*GoupC63rht{&1+3&xEOk+=RSpUj8kqyDW$CbFn;KDob7eDRi|0aK zly4p@Sv(iYqF5rD4AFYIf4S@h6RIHV_O*1;!)x)wN>uS`RN5|2odN;CR>|YAqIGwN z;}b$XSStqZWpnYkj{I{=`DaR;opE)2hO;GOd{%hgpuJdMG@zD>R{UpIUU^vv!5$X-Cc0l$U`lm7&EM}E z(F@ENbZbqITa(^)_X^g=@dLoxu}M4m;V*x#AN=LEm*tJ>({{NPo|ZIAref)GLsC+~ z%7L!|326fOFjcb!e?8JPTjq0$?Rb)UfO3PLc%uIiv#M8gclG7pmwB2mo2jYYVGm!f zOmr*Yr9=z*0^64L?=eTdYW*q9_hoF=u?ic_`)(wC`z?K04{aj&Y0>mu*QUJo^*}-o zEy(3KwbSqZGGl)o1^3PLx!D>BTb^Z5_l#!*yCf5Km)Wdt`{G8|8hN;O$VXcDqt76VVw;KG{_2eRIo{v>#;@|dRC*enH~X1W67ykDa_4q^ zpMJlE>(NycZk!OVzmq@OZ&Ofj_sNm3-W&UUCd=ZN=zWT4SKj8|EjmdFF0rQpr^HW( zx#0H&6}v6z*IOGqhb9B%;^NBNMtSvpuY=ea>~exnkvTDT#||T>wl^yN-WSW8OIGHY zDg$^&8as^gHYbY{uX~fl&4M+5-q?QlM052{ACIX~AYmNWL$sU9FB(`qiIuMXftk2r zXjaYJSLi(k7Nwt-g+4i|UvBm*={RZek8mHlGx}5cr}8zGif#O30bYjkz2?uS;@XaP zHDndG=!*FV1t+FPQ`glbM6!>!8AD?>NoL|a8!^r;I`As-hz4b_{5{Jd*cxr|TepQ2<(y_5IsY!>ul@0)tx<^dbw4>>h!r@s?i zBvzkyt4sW5eTd}z{+QC@f7y4giW_lrC}hj`I6!rM#uWEG4ozAaIKARKmNGxPG{1gJbf95ae?{2OI5e|2#xjin z1Q^@O*^nnOU9Dw=HqN$;e(Cn0)ZG3u-MP=C#6+Nyc=!I!%C)l_Dgu(X{?qYT49qfK zsN6<2Dd7i$2iM_?R<-AT8`_;<88lt`Q0ji+fy=|Jh7Sx|xgc_kCb-jjq?UzKOx%=Is&dZK v5=sy7|4o1Vm(dPDr+a?b{cb+nrC$P6{_-Mci+kNo>mQlNjo^G5l4$rJyOJrJ literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-nodpi-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-nodpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-nodpi-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-nodpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a750f2e1b9d222e4cc1f6f21a5fa0bd9b2b99cbf GIT binary patch literal 297 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 literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-nodpi-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-nodpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_space.png new file mode 100644 index 0000000000000000000000000000000000000000..925b7acfbe999101c37145f13533b6c486a67d89 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0y~yV0;2(J8&=q$v<6R+JTf=fKP}kkbXxx*rF!70;p4{ zB*-rqXfz9}XlQA*czH)tS#5(vnBktquYj_*JY5_^DsH{K?OoJtAmAEk`Y8KA=lA_L zcNl%%ke+9e&Ut6kf>Zu$8G%YcpoXLCq@#?1*)t~0ttWyf_-qUyTeD*|b zw>+h8olPe?dY}l%Gg4AEx-WY(zE@Sb{o;YE%KElv>euPo|Cda%=KYz)`%`ZJ&ED`M zUyM$!H91+kRDI{MujUfhcdHdQXB+A5x%=>tG{l_@1%>rZ)nPSVCI?(VBA%{(F6*2U FngI39w0i&m literal 0 HcmV?d00001 diff --git a/leankeykeyboard/src/main/res/drawable-nodpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-nodpi-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-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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 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/activity_kb_layout.xml b/leankeykeyboard/src/main/res/layout/activity_kb_layout.xml new file mode 100644 index 0000000..e5b1b02 --- /dev/null +++ b/leankeykeyboard/src/main/res/layout/activity_kb_layout.xml @@ -0,0 +1,8 @@ + + + \ 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..8f78be5 --- /dev/null +++ b/leankeykeyboard/src/main/res/layout/candidate.xml @@ -0,0 +1,5 @@ + + +