├── .gitignore ├── Android.bp ├── AndroidManifest.xml ├── README.md ├── privapp_whitelist_org.protonaosp.elmyra.xml ├── proguard.flags ├── res ├── drawable │ └── ic_outline_pan_tool_24.xml ├── layout │ ├── preference_labeled_slider.xml │ └── settings_activity.xml ├── values-ast-rES │ └── strings.xml ├── values-az │ └── strings.xml ├── values-bn │ └── strings.xml ├── values-ca │ └── strings.xml ├── values-da │ └── strings.xml ├── values-de │ └── strings.xml ├── values-el │ └── strings.xml ├── values-en-rAU │ └── strings.xml ├── values-en-rCA │ └── strings.xml ├── values-en-rGB │ └── strings.xml ├── values-en-rIN │ └── strings.xml ├── values-fr │ └── strings.xml ├── values-gd │ └── strings.xml ├── values-gl │ └── strings.xml ├── values-hu │ └── strings.xml ├── values-in │ └── strings.xml ├── values-it │ └── strings.xml ├── values-ja │ └── strings.xml ├── values-ko │ └── strings.xml ├── values-nl │ └── strings.xml ├── values-pt-rBR │ └── strings.xml ├── values-pt-rPT │ └── strings.xml ├── values-ro │ └── strings.xml ├── values-ru │ └── strings.xml ├── values-sl │ └── strings.xml ├── values-sq │ └── strings.xml ├── values-th │ └── strings.xml ├── values-tr │ └── strings.xml ├── values-zh-rCN │ └── strings.xml ├── values-zh-rTW │ └── strings.xml ├── values │ ├── attrs.xml │ ├── config.xml │ └── strings.xml └── xml │ └── settings.xml └── src ├── com └── android │ └── settings │ └── widget │ ├── DefaultIndicatorSeekBar.java │ ├── LabeledSeekBarPreference.java │ └── SeekBarPreference.java └── org └── protonaosp └── elmyra ├── BootReceiver.kt ├── Constants.kt ├── ElmyraService.kt ├── MessageType.kt ├── Preferences.kt ├── actions ├── Action.kt ├── AssistantAction.kt ├── CameraAction.kt ├── DummyAction.kt ├── FlashlightAction.kt ├── MuteAction.kt ├── PowerMenuAction.kt ├── ScreenAction.kt └── ScreenshotAction.kt ├── proto └── chre_messages.proto └── settings ├── SearchProvider.kt ├── SettingsActivity.kt ├── SettingsFragment.kt ├── SummaryProvider.kt └── ToggleTileService.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project 2 | .idea/ 3 | *.iml 4 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | android_app { 16 | name: "ElmyraService", 17 | srcs: ["src/**/*.java", "src/**/*.kt", "src/**/*.proto"], 18 | resource_dirs: ["res"], 19 | platform_apis: true, 20 | system_ext_specific: true, 21 | certificate: "platform", 22 | privileged: true, 23 | required: ["privapp_whitelist_org.protonaosp.elmyra"], 24 | static_libs: [ 25 | "kotlin-stdlib", 26 | "androidx.preference_preference", 27 | ], 28 | defaults: [ 29 | "SettingsLibDefaults", 30 | ], 31 | optimize: { 32 | proguard_flags_files: ["proguard.flags"], 33 | }, 34 | proto: { 35 | type: "nano", 36 | }, 37 | } 38 | 39 | prebuilt_etc { 40 | name: "privapp_whitelist_org.protonaosp.elmyra", 41 | system_ext_specific: true, 42 | sub_dir: "permissions", 43 | src: "privapp_whitelist_org.protonaosp.elmyra.xml", 44 | filename_from_src: true, 45 | } 46 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Active Edge Service 2 | 3 | This is an open-source implementation of the Pixel Active Edge gesture, written from scratch for portability and customizability. 4 | 5 | Active Edge, codename Elmyra, is a gesture powered by side grip sensors on the Pixel 2/XL, 3/XL, 3a/XL, and 4/XL that is used to activate the Google Assistant on stock. The gesture is handled by a proprietary nanoapp running under CHRE (ContextHub Runtime Environment) on the sensor DSP. 6 | 7 | This app is a reverse-engineered Android client that runs as a standalone service and talks to the CHRE nanoapp for gesture functionality. No decompiled code has been used. 8 | 9 | ## Features 10 | 11 | - Seamless integration in Settings → System → Gestures → Active Edge with no extra changes 12 | - Integration with Settings search 13 | - Many actions to perform on gesture trigger 14 | - Take screenshot 15 | - Open assistant 16 | - Open camera 17 | - Toggle flashlight 18 | - Mute calls & notifications (replicates default power + volume-up "prevent ringing" gesture) 19 | - Toggle power menu 20 | - Toggle screen 21 | - 10 sensitivity levels (more granular than stock) 22 | - Setting to control whether gesture is enabled when the screen is off 23 | - Contextually-appropriate haptic feedback with modern effects 24 | - Heavy click for squeeze 25 | - Click for release 26 | - Reject for unavailable action (e.g. if flashlight can't turn on because camera is in use) 27 | 28 | ## Integration 29 | 30 | Sync this repo to packages/apps/ElmyraService. 31 | 32 | Add the following to your device tree **only for devices with this feature**: 33 | ```make 34 | # Active Edge 35 | PRODUCT_PACKAGES += \ 36 | ElmyraService 37 | ``` 38 | 39 | While this service is designed to be as portable and self-contained as possible, Android does not provide the necessary APIs to implement all gesture actions out-of-the-box. This means that some commits must be added to frameworks/base to expose the APIs for full functionality: 40 | 41 | - For screenshot action: [SystemUI: Allow privileged system apps to access screenshot service](https://github.com/ProtonAOSP/android_frameworks_base/commit/013c590411435569077228aacf1e246678c366ab) 42 | - For assistant action: [core: Expose method to start assistant through Binder](https://github.com/ProtonAOSP/android_frameworks_base/commit/2b950e103e865aa6a1fe8a917964e0069d4c4037) 43 | 44 | Default settings can be changed by overlaying [res/values/config.xml](https://github.com/ProtonAOSP/android_packages_apps_ElmyraService/blob/rvc/res/values/config.xml). 45 | 46 | ## License 47 | 48 | All code in this repository is licensed under the Apache 2.0 License. 49 | -------------------------------------------------------------------------------- /privapp_whitelist_org.protonaosp.elmyra.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /proguard.flags: -------------------------------------------------------------------------------- 1 | # This is a configuration file for ProGuard. 2 | # http://proguard.sourceforge.net/index.html#manual/usage.html 3 | 4 | # Keep classes that may be inflated from XML. 5 | -keepclasseswithmembers class * { 6 | public (android.content.Context, android.util.AttributeSet); 7 | } 8 | -keepclasseswithmembers class * { 9 | public (android.content.Context, android.util.AttributeSet, int); 10 | } 11 | 12 | # Keep annotated classes or class members. 13 | -keep @androidx.annotation.Keep class * 14 | -keepclassmembers class * { 15 | @androidx.annotation.Keep *; 16 | } 17 | -------------------------------------------------------------------------------- /res/drawable/ic_outline_pan_tool_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /res/layout/preference_labeled_slider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 28 | 29 | 39 | 40 | 49 | 50 | 55 | 56 | 63 | 64 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /res/values-ast-rES/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Serviciu «Active Edge» 19 | Active Edge 20 | Sí / %1$s 21 | Non 22 | Active Edge, berbesu, xestu, asistente, asistir, apertar, apertamientu, presión 23 | Apertar el teléfonu pa facer una aición 24 | Pa facer dalguna aición, apierta y suelta los berbesos del teléfonu cierca de la parte baxera 25 | Sensibilidá 26 | Presión llixera 27 | Presión fuerte 28 | Permitir cola pantalla apagada 29 | Pue aumentar les aiciones accidentales 30 | L\'aición actual bloquia la opción 31 | Al apertar 32 | Facer una captura de pantalla 33 | Abrir l\'asistente 34 | Abrir la cámara 35 | Abrir el menú d\'apagáu 36 | Desactivar el soníu de les llamaes y los avisos 37 | Prender/apagar la llinterna 38 | Prender/apagar la pantalla 39 | 40 | -------------------------------------------------------------------------------- /res/values-az/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Aktiv Edge Xidməti 19 | Aktiv Edge 20 | Açıq / %1$s 21 | Bağlı 22 | Aktiv Edge, sıxma, künc, jest, kömək, köməkçi 23 | Əməliyyatı icra etmək üçün telefonu sıxın 24 | Bir əməliyyat icra etmək üçün telefonun aşağı hissəsinin kənarlarını sıxıb buraxın 25 | Həssaslıq 26 | Yüngül sıxma 27 | Möhkəm sıxma 28 | Ekran sönülü olanda icazə ver 29 | Yanlış tətikləyiciləri artıra bilər 30 | Hazırkı əməliyyat tərəfindən əngəlləndi 31 | Sıxanda 32 | Ekran şəklini çək 33 | Köməkçini aç 34 | Kameranı aç 35 | Güc menyusunu aç 36 | Zəngləri və bildirişləri səssizə al 37 | Fənəri yandır/söndür 38 | Ekranı aç/bağla 39 | 40 | -------------------------------------------------------------------------------- /res/values-bn/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | সক্রিয় এজ সার্ভিস 19 | সক্রিয় এজ 20 | চালু / %1$s 21 | বন্ধ 22 | সক্রিয় এজ, স্কুইজ, এজ, জেসচার, অ্যাসিস্ট, অ্যাসিস্ট্যান্ট 23 | ক্রিয়া সম্পাদনের জন্য ফোন স্কুইজ করুন 24 | ক্রিয়া সম্পাদনের জন্য ফোনের নিচের অংশে স্কুইজ করে ছেড়ে দিন 25 | সেনসিটিভিটি 26 | হালকা স্কুইজ 27 | দৃঢ় স্কুইজ 28 | স্ক্রিন বন্ধ অবস্থায় অনুমতি দেন 29 | ভুলে চালু হওয়ার প্রবণতা বেড়ে যেতে পারে 30 | বর্তমান কার্যক্রমে ব্লক করা হয়েছে 31 | স্কুইজ করলে 32 | স্ক্রীনশট নিন 33 | অ্যাসিস্ট্যান্ট খুলুন 34 | ক্যামেরা খুলুন 35 | পাওয়ার মেনু খুলুন 36 | কল এবং বিজ্ঞপ্তি মিউট করুন 37 | ফ্ল্যাশলাইট অন/অফ করুন 38 | স্ক্রীন অন/অফ করুন 39 | 40 | -------------------------------------------------------------------------------- /res/values-ca/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Servei de l\'Active Edge 19 | Active Edge 20 | Actiu / %1$s 21 | Desactivat 22 | Active Edge, aixafa, vora, gest, ajudar, ajudant 23 | Aixafeu el telèfon per realitzar accions 24 | Per realitzar una acció, aixafeu i deixeu anar els costats del telèfon a prop de la part inferior 25 | Sensibilitat 26 | Aixafat lleuger 27 | Aixafat ferm 28 | Permetre amb la pantalla apagada 29 | Pot augmentar els desencadenants falsos 30 | Bloquejat per l\'acció actual 31 | En aixafar 32 | Fes una captura de pantalla 33 | Obre l\'assistent 34 | Obre la càmera 35 | Obre el menú d\'energia 36 | Silencia les trucades i notificacions 37 | Llanterna encesa/apagada 38 | Pantalla encesa/apagada 39 | 40 | -------------------------------------------------------------------------------- /res/values-da/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Service For Aktiv Kant 19 | Aktiv Kant 20 | Til / %1$s 21 | Slukket 22 | Aktiv kant, klemme, kant, gestus, assistent, assistent 23 | Klem telefonen for at udføre handling 24 | For at udføre en handling, klem og slip siderne af din telefon nær bunden 25 | Sensitivitet 26 | Let klem 27 | Fast klem 28 | Tillad, når skærmen er slukket 29 | Kan øge falske udløsere 30 | Blokeret af aktuel handling 31 | Ved pres 32 | Tag skærmbillede 33 | Åbn assistent 34 | Åbn kamera 35 | Åbn tænd/sluk-menu 36 | Lydløs opkald og notifikationer 37 | Lommelygte til/fra 38 | Tænd/sluk skærm 39 | 40 | -------------------------------------------------------------------------------- /res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge-Dienst 19 | Active Edge 20 | Bei / %1$s 21 | Aus 22 | Active Edge, Zusammendrücken, Rand, Geste, Assistent 23 | Gerät zusammendrücken, um eine Aktion auszulösen 24 | Untere Hälfte des Geräts kurz zusammendrücken und loslassen, um eine Aktion auszulösen 25 | Empfindlichkeit 26 | Leicht drücken 27 | Stark drücken 28 | Bei deaktiviertem Bildschirm zulassen 29 | Kann zu Fehlauslösungen führen 30 | Durch die aktuelle Aktion blockiert 31 | Beim Zusammendrücken 32 | Screenshot erstellen 33 | Assistent öffnen 34 | Kamera öffnen 35 | Ein-/Aus-Menü öffnen 36 | Anrufe und Benachrichtigungen stummschalten 37 | Taschenlampe ein-/ausschalten 38 | Bildschirm ein-/ausschalten 39 | 40 | -------------------------------------------------------------------------------- /res/values-el/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Υπηρεσία Active Edge 19 | Active Edge 20 | Ενεργό / %1$s 21 | Aνενεργό 22 | Active Edge, πίεση, άκρη, χειρονομία, βοήθεια, βοηθός 23 | Πιέστε το τηλέφωνο για να εκτελέσετε ενέργεια 24 | Για να εκτελέσετε μια ενέργεια, πιέστε και απελευθερώστε τις πλευρές του τηλεφώνου σας κοντά στο κάτω μέρος 25 | Ευαισθησία 26 | Ελαφριά συμπίεση 27 | Σφιχτή συμπίεση 28 | Επιτρέπεται όταν η οθόνη είναι απενεργοποιημένη 29 | Μπορεί να αυξήσει τα ψευδή εναύσματα 30 | Αποκλείστηκε από την τρέχουσα ενέργεια 31 | Κατά την πίεση 32 | Λήψη στιγμιότυπου οθόνης 33 | Άνοιγμα βοηθού 34 | Άνοιγμα φωτογραφικής μηχανής 35 | Άνοιγμα μενού απενεργοποίησης 36 | Σίγαση κλήσεων και ειδοποιήσεων 37 | Ενεργοποίηση/απενεργοποίηση φακού 38 | Ενεργοποίηση/απενεργοποίηση οθόνης 39 | 40 | -------------------------------------------------------------------------------- /res/values-en-rAU/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge Service 19 | Active Edge 20 | On / %1$s 21 | Off 22 | Active Edge, squeeze, edge, gesture, assist, assistant 23 | Squeeze phone to perform action 24 | To perform an action, squeeze and release the sides of your phone near the bottom 25 | Sensitivity 26 | Light squeeze 27 | Firm squeeze 28 | Allow when screen is off 29 | May increase false triggers 30 | Blocked by current action 31 | On squeeze 32 | Take screenshot 33 | Open assistant 34 | Open camera 35 | Open power menu 36 | Mute calls and notifications 37 | Turn torch on/off 38 | Turn screen on/off 39 | 40 | -------------------------------------------------------------------------------- /res/values-en-rCA/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge Service 19 | Active Edge 20 | On / %1$s 21 | Off 22 | Active Edge, squeeze, edge, gesture, assist, assistant 23 | Squeeze phone to perform action 24 | To perform an action, squeeze and release the sides of your phone near the bottom 25 | Sensitivity 26 | Light squeeze 27 | Firm squeeze 28 | Allow when screen is off 29 | May increase false triggers 30 | Blocked by current action 31 | On squeeze 32 | Take screenshot 33 | Open assistant 34 | Open camera 35 | Open power menu 36 | Mute calls and notifications 37 | Turn torch on/off 38 | Turn screen on/off 39 | 40 | -------------------------------------------------------------------------------- /res/values-en-rGB/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge Service 19 | Active Edge 20 | On / %1$s 21 | Off 22 | Active Edge, squeeze, edge, gesture, assist, assistant 23 | Squeeze phone to perform action 24 | To perform an action, squeeze and release the sides of your phone near the bottom 25 | Sensitivity 26 | Light squeeze 27 | Firm squeeze 28 | Allow when screen is off 29 | May increase false triggers 30 | Blocked by current action 31 | On squeeze 32 | Take screenshot 33 | Open assistant 34 | Open camera 35 | Open power menu 36 | Mute calls and notifications 37 | Turn torch on/off 38 | Turn screen on/off 39 | 40 | -------------------------------------------------------------------------------- /res/values-en-rIN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge Service 19 | Active Edge 20 | On / %1$s 21 | Off 22 | Active Edge, squeeze, edge, gesture, assist, assistant 23 | Squeeze phone to perform action 24 | To perform an action, squeeze and release the sides of your phone near the bottom 25 | Sensitivity 26 | Light squeeze 27 | Firm squeeze 28 | Allow when screen is off 29 | May increase false triggers 30 | Blocked by current action 31 | On squeeze 32 | Take screenshot 33 | Open assistant 34 | Open camera 35 | Open power menu 36 | Mute calls and notifications 37 | Turn torch on/off 38 | Turn screen on/off 39 | 40 | -------------------------------------------------------------------------------- /res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Service Active Edge 19 | Active Edge 20 | Activé / %1$s 21 | Désactivé 22 | Active Edge, pression, bords, gestes, assistance, assistant 23 | Pressez le téléphone pour effectuer une action 24 | Pour effectuer une action, pressez et relâchez les côtés de la partie inférieure de votre téléphone 25 | Sensibilité 26 | Pression légère 27 | Pression ferme 28 | Autoriser lorsque l\'écran est éteint 29 | Augmente le risque de déclenchement involontaire 30 | Bloqué par l\'action en cours 31 | En pressant 32 | Effectuer une capture d\'écran 33 | Ouvrir l\'assistant 34 | Ouvrir l\'appareil photo 35 | Ouvrir le menu marche/arrêt 36 | Mettre les appels et les notifications en sourdine 37 | Allumer/éteindre la lampe torche 38 | Allumer/éteindre l\'écran 39 | 40 | -------------------------------------------------------------------------------- /res/values-gd/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Seirbheis Active Edge 19 | Active Edge 20 | Air / %1$s 21 | Dheth 22 | Active Edge, squeeze, edge, gesture, assist, assistant, dinn, oir, gluasad, cuidich, chuidiche 23 | Dinn am fòn airson gnìomh a ghabhail 24 | Airson gnìomh a ghabhail, dinn is leig às cliathaichean an fhòn agad faisg air a’ bhonn 25 | Mothalachd 26 | Dinneadh lag 27 | Dinneadh teann 28 | Ceadaich nuair a bhios an sgrìn dheth 29 | Faodaidh seo nithean a chur gu dol gun iarraidh nas trice 30 | Air a bhacadh leis a’ ghnìomh làithreach 31 | Le dinneadh 32 | Tog glacadh-sgrìn 33 | Fosgail an cuidiche 34 | Fosgail an camara 35 | Fosgail clàr-taice na cumhachd 36 | Mùch gairmean is brathan 37 | Cuir an leus air/dheth 38 | Cuir an sgrìn air/dheth 39 | 40 | -------------------------------------------------------------------------------- /res/values-gl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Servizo de marxe activa 19 | Marxe activa 20 | Activado / %1$s 21 | Desactivado 22 | Marxe activa, premer, marxe, xesto, asistencia, asistente 23 | Premer no teléfono para realizar unha acción 24 | Para realizar unha acción, preme e solta os lados da parte inferior do teléfono 25 | Sensibilidade 26 | Presión leve 27 | Presión firme 28 | Permitir coa pantalla apagada 29 | Pode causar activacións accidentais 30 | Bloqueado pola acción actual 31 | Ao premer 32 | Capturar pantalla 33 | Abrir asistente 34 | Abrir cámara 35 | Abrir menú de activar/desactivar 36 | Silenciar chamadas e notificacións 37 | Lanterna encendida/apagada 38 | Pantalla encendida/apagada 39 | 40 | -------------------------------------------------------------------------------- /res/values-hu/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Aktív készülékszél szolgáltatás 19 | Aktív készülékszélek 20 | %1$s / Be 21 | Ki 22 | Aktív készülékszél, összenyomás, szélek, mozdulatvezérlés, segéd 23 | Művelet a készülékszélek összenyomásával 24 | Művelet végrehajtásához nyomja össze majd engedje el a telefon széleit a telefon alja közelében 25 | Érzékenység 26 | Enyhe összenyomás 27 | Határozott összenyomás 28 | Engedély alvó képernyőn is 29 | Növelheti a hamis indításokat 30 | A jelenlegi művelet blokkolja 31 | Összenyomáskor 32 | Képernyőmentés 33 | Asszisztens-indítás 34 | Kameraindítás 35 | Kikapcsolómenü-megnyitás 36 | Hívások és értesítések elhalkítása 37 | Zseblámpa be/ki 38 | Képernyő ki/bekapcsolása 39 | 40 | -------------------------------------------------------------------------------- /res/values-in/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Sensitivitas 19 | Izinkan saat layar mati 20 | Buka asisten 21 | Buka kamera 22 | Buka menu daya 23 | 24 | -------------------------------------------------------------------------------- /res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Servizio Active Edge 19 | Active Edge 20 | On / %1$s 21 | Off 22 | Active Edge, stringi, bordi, gesti, assistente, assistenza 23 | Stringi il telefono per attivare una funzione 24 | Per effettuare un\'azione, stringi e rilascia la parte bassa dei lati del telefono 25 | Sensibilità 26 | Pressione leggera 27 | Pressione forte 28 | Permetti quando lo schermo è spento 29 | Potrebbe aumentare le attivazioni involontarie 30 | Bloccato dall\'azione attuale 31 | Quando si stringe 32 | Cattura schermata 33 | Apri l\'assistente 34 | Apri fotocamera 35 | Apri menù di spegnimento 36 | Silenzia chiamate e notifiche 37 | Accendi/spegni torcia 38 | Accendi/spegni lo schermo 39 | 40 | -------------------------------------------------------------------------------- /res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge サービス 19 | Active Edge 20 | ON / %1$s 21 | OFF 22 | Active Edge、握る、エッジ、ジェスチャー、アシスト、アシスタント 23 | 端末を握ってアクションを起動 24 | アクションを起動するには、端末のボタン付近の両端を握った後に離してください。 25 | 感度 26 | 軽い握り 27 | 強い握り 28 | 画面消灯中は許可 29 | 誤作動が増えることがあります 30 | 現在の操作でブロックされました 31 | 握った時 32 | スクリーンショットを撮影 33 | アシスタントを開く 34 | カメラを起動 35 | 電源メニューを開く 36 | 着信と通知をミュート 37 | ライトの ON/OFF 38 | 画面を消灯/点灯 39 | 40 | -------------------------------------------------------------------------------- /res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge 서비스 19 | Active Edge 20 | 켜짐 / %1$s 21 | 꺼짐 22 | Active Edge, 쥐기, 엣지, 제스처, 조수, 어시스턴트, 도움 23 | 기기 양쪽을 쥐어 작업 실행 24 | 작업을 실행하려면 기기 양쪽의 아랫부분을 쥐었다 놓으세요. 25 | 민감도 26 | 가볍게 쥐기 27 | 단단히 쥐기 28 | 화면이 꺼져 있을 때 허용 29 | 잘못된 동작이 늘어날 수 있습니다. 30 | 이 행동으로 막힘 31 | 쥐었을 때 32 | 화면 캡처 33 | 어시스턴트 열기 34 | 카메라 열기 35 | 전원 메뉴 열기 36 | 전화 및 알림 음소거 37 | 손전등 켜기/끄기 38 | 화면 켜기/끄기 39 | 40 | -------------------------------------------------------------------------------- /res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Actieve Edge Service 19 | Actieve Edge 20 | Aan / %1$s 21 | Uit 22 | Actieve Edge, squeeze, randen, gebaar, helpen, assistent 23 | Knijp in toestel om actie uit te voeren 24 | Om een actie uit te voeren, knijp in de zijkant van je toestel nabij de onderkant 25 | Gevoeligheid 26 | Licht knijpen 27 | Stevig knijpen 28 | Toestaan wanneer het scherm is uitgeschakeld 29 | Kan valse triggers opleveren 30 | Geblokkeerd door huidige actie 31 | Bij knijpen 32 | Maak schermafbeelding 33 | Open assistent 34 | Open camera 35 | Open aan/uit menu 36 | Oproepen en meldingen zonder geluid 37 | Zaklamp aan/uit zetten 38 | Scherm aan/uit zetten 39 | 40 | -------------------------------------------------------------------------------- /res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Sensibilidade 19 | Capturar tela 20 | Abrir assistente 21 | Abrir câmera 22 | Silenciar chamadas e notificações 23 | Ligar/desligar a lanterna 24 | Ligar/desligar a tela 25 | 26 | -------------------------------------------------------------------------------- /res/values-pt-rPT/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Serviço Active Edge 19 | Active Edge 20 | Ligado / %1$s 21 | Desligado 22 | Active Edge, apertar, borda, gesto, assistência, assistente 23 | Apertar o telefone para executar uma ação 24 | Para executar uma ação, aperte e solte os lados do seu telefone perto da parte inferior 25 | Sensibilidade 26 | Aperto ligeiro 27 | Aperto firme 28 | Permitir com ecrã desligado 29 | Pode implicar ativações indesejadas 30 | Bloqueado pela ação atual 31 | Ao apertar 32 | Realizar captura de ecrã 33 | Abrir o Assistente 34 | Abrir câmara 35 | Abrir menu de ligar/desligar 36 | Silenciar chamadas e notificações 37 | Ligar/desligar lanterna 38 | Ligar/desligar ecrã 39 | 40 | -------------------------------------------------------------------------------- /res/values-ro/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Serviciu Active Edge 19 | Active Edge 20 | Activ / %1$s 21 | Oprit 22 | Active Edge, strângere, margine, gest, asistență, asistent 23 | Strânge telefonul pentru a realiza acțiunea 24 | Pentru a efectua o acțiune, strângeți și eliberați marginile telefonului din partea de jos 25 | Sensibilitate 26 | Strângere ușoară 27 | Strângere fermă 28 | Permite când ecranul este oprit 29 | Poate crește declanșările false 30 | Blocat de acțiunea curentă 31 | La strângere 32 | Captură ecran 33 | Deschide asistentul 34 | Deschide camera 35 | Deschide meniul de alimentare 36 | Pune pe mut apelurile și notificările 37 | Pornește lanterna 38 | Pornește/stinge ecranul 39 | 40 | -------------------------------------------------------------------------------- /res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Служба Active Edge 19 | Active Edge 20 | Вкл. / %1$s 21 | Выкл. 22 | Active Edge, сжатие, edge, жест, помощь, ассистент 23 | Для выполнения действия сожмите телефон 24 | Для выполнения действия сожмите и отпустите боковые стороны телефона в нижней части корпуса. 25 | Чувствительность 26 | Легкое сжатие 27 | Крепкое сжатие 28 | Разрешить при выключенном экране 29 | Может увеличить количество ложных срабатываний 30 | Заблокировано текущим действием 31 | При сжатии 32 | Сделать скриншот 33 | Открыть ассистента 34 | Открыть камеру 35 | Открыть меню питания 36 | Отключить звук звонков и уведомлений 37 | Включить/выключить фонарик 38 | Включить/выключить экран 39 | 40 | -------------------------------------------------------------------------------- /res/values-sl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Storitev aktivnega roba 19 | Aktivni rob 20 | Vklopljen / %1$s 21 | Izklopljen 22 | Aktivni rob, stisk, rob, poteza, pomoč, pomočnik 23 | Stisnite telefon za izvedbo dejanja 24 | Za izvedbo dejanja, stisnite in izpustite stranici telefona blizu dna 25 | Občutljivost 26 | Nežen stisk 27 | Čvrst stisk 28 | Dovoli, ko je zaslon izklopljen 29 | Lahko poveča lažne sprožitve 30 | Blokirano s trenutnim dejanjem 31 | Ob stisku 32 | Zajemi zaslonsko sliko 33 | Odpri pomočnika 34 | Odpri fotoaparat 35 | Odpri meni izklopa 36 | Utišaj klice in obvestila 37 | Vklopi/izklopi svetilko 38 | Vklopi/izklopi zaslon 39 | 40 | -------------------------------------------------------------------------------- /res/values-sq/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Shërbimi Edge Aktiv 19 | Edge aktive 20 | Ndezur / %1$s 21 | Fikur 22 | Edge aktive, shtrydhje, Edge, gjest, asistent, asistent 23 | Shtrydhni telefonin për të kryer veprimin 24 | Për të kryer një veprim, shtrydhni dhe lëshoni anët e telefonit tuaj pranë fundit 25 | Ndjeshmëria 26 | Shtrydhje e lehtë 27 | Shtrydhje e fortë 28 | Lejo kur ekrani është i fikur 29 | Mund të rrisë shkaktarët e rremë 30 | Bllokuar nga veprimi aktual 31 | Në shtrydhje 32 | Bëj foto të ekranit 33 | Hap asistentin 34 | Hap kameren 35 | Hapni menunë e energjisë 36 | Hesht thirrjet dhe njoftimet 37 | Ndizni/fikni elektrikun e dorës 38 | Ndizni/fikni ekranin 39 | 40 | -------------------------------------------------------------------------------- /res/values-th/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | บริการ Active Edge 19 | เมื่อ / %1$s 20 | ปิด 21 | บีบเบาๆ 22 | บีบแน่นๆ 23 | อนุญาตเมื่อจอดับอยู่ 24 | เมื่อบีบ 25 | จับภาพหน้าจอ 26 | เปิด assistant 27 | เปิดกล้อง 28 | ปิดเสียงสายเรียกเข้าและการแจ้งเตือน 29 | เปิด/ปิด ไฟฉาย 30 | เปิด/ปิด หน้าจอ 31 | 32 | -------------------------------------------------------------------------------- /res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Aktif Kenar Hizmeti 19 | Aktif Kenar 20 | Açık / %1$s 21 | Kapalı 22 | Active Edge, sıkma, kenar, hareket, yardım, asistan 23 | İşlemi gerçekleştirmek için telefonu sıkın 24 | Bir işlem gerçekleştirmek için telefonun alt kısmının kenarlarını sıkıştırın 25 | Hassasiyet 26 | Hafif sıkma 27 | Sıkı sıkma 28 | Ekran kapalıyken izin ver 29 | Yanlış tetikleyicileri artırabilir 30 | Mevcut işlem tarafından engellendi 31 | Sıkmak 32 | Ekran görüntüsü al 33 | Asistanı aç 34 | Kamerayı aç 35 | Güç menüsünü aç 36 | Çağrıları ve bildirimleri sessize al 37 | El fenerini aç/kapat 38 | Ekranı aç/kapat 39 | 40 | -------------------------------------------------------------------------------- /res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 边缘压感服务 19 | 边缘压感 20 | 开启/ %1$s 21 | 关闭 22 | 活动边缘、握压、边缘、手势、辅助、助手 23 | 握压手机来执行操作 24 | 要执行操作,请握压并松开靠近底部的手机侧面 25 | 敏感度 26 | 轻轻握压 27 | 用力握压 28 | 屏幕关闭时允许 29 | 可能增加误触 30 | 被当前操作阻止 31 | 握压操作 32 | 截图 33 | 打开助理 34 | 打开相机 35 | 打开电源菜单 36 | 来电及通知静音 37 | 打开/关闭手电筒 38 | 打开/关闭屏幕 39 | 40 | -------------------------------------------------------------------------------- /res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 活動邊緣服務 19 | 活動邊緣 20 | 開啟/%1$s 21 | 關閉 22 | 活動邊緣、擠壓、邊緣、手勢、輔助、助理 23 | 擠壓手機執行動作 24 | 執行動作, 擠壓並釋放近手機底部兩邊 25 | 靈敏度 26 | 輕壓 27 | 硬壓 28 | 當螢幕關閉時允許 29 | 可能會增加誤觸 30 | 被當前動作阻止 31 | 擠壓時 32 | 螢幕擷取畫面 33 | 開啟助理 34 | 開啟相機 35 | 開啟電源選單 36 | 靜音通話和通知 37 | 開啟/關閉手電筒 38 | 開啟/關閉螢幕 39 | 40 | -------------------------------------------------------------------------------- /res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | true 19 | 5 20 | screenshot 21 | true 22 | 23 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Active Edge Service 19 | 20 | 21 | enabled 22 | sensitivity 23 | action 24 | allow_screen_off 25 | allow_screen_off_action_forced 26 | 27 | 28 |   29 | 30 | 31 | Active Edge 32 | On / %1$s 33 | Off 34 | Active Edge, squeeze, edge, gesture, assist, assistant 35 | 36 | 37 | Squeeze phone to perform action 38 | To perform an action, squeeze and release the sides of your phone near the bottom 39 | Sensitivity 40 | Light squeeze 41 | Firm squeeze 42 | Allow when screen is off 43 | May increase false triggers 44 | Blocked by current action 45 | On squeeze 46 | 47 | 48 | Take screenshot 49 | Open assistant 50 | Open camera 51 | Open power menu 52 | Mute calls and notifications 53 | Turn flashlight on/off 54 | Turn screen on/off 55 | 56 | 57 | 58 | screenshot 59 | assistant 60 | camera 61 | power_menu 62 | mute 63 | flashlight 64 | screen 65 | 66 | 67 | @string/action_screenshot_name 68 | @string/action_assistant_name 69 | @string/action_camera_name 70 | @string/action_power_menu_name 71 | @string/action_mute_name 72 | @string/action_flashlight_name 73 | @string/action_screen_name 74 | 75 | 76 | -------------------------------------------------------------------------------- /res/xml/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/com/android/settings/widget/DefaultIndicatorSeekBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.settings.widget; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.drawable.Drawable; 22 | import android.util.AttributeSet; 23 | import android.widget.SeekBar; 24 | 25 | public class DefaultIndicatorSeekBar extends SeekBar { 26 | 27 | private int mDefaultProgress = -1; 28 | 29 | public DefaultIndicatorSeekBar(Context context) { 30 | super(context); 31 | } 32 | 33 | public DefaultIndicatorSeekBar(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | } 36 | 37 | public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | } 40 | 41 | public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr, 42 | int defStyleRes) { 43 | super(context, attrs, defStyleAttr, defStyleRes); 44 | } 45 | 46 | /** 47 | * N.B. Only draws the default indicator tick mark, NOT equally spaced tick marks. 48 | */ 49 | @Override 50 | protected void drawTickMarks(Canvas canvas) { 51 | if (isEnabled() && mDefaultProgress <= getMax() && mDefaultProgress >= getMin()) { 52 | final Drawable defaultIndicator = getTickMark(); 53 | 54 | // Adjust the drawable's bounds to center it at the point where it's drawn. 55 | final int w = defaultIndicator.getIntrinsicWidth(); 56 | final int h = defaultIndicator.getIntrinsicHeight(); 57 | final int halfW = w >= 0 ? w / 2 : 1; 58 | final int halfH = h >= 0 ? h / 2 : 1; 59 | defaultIndicator.setBounds(-halfW, -halfH, halfW, halfH); 60 | 61 | // This mimics the computation of the thumb position, to get the true "default." 62 | final int availableWidth = getWidth() - mPaddingLeft - mPaddingRight; 63 | final int range = getMax() - getMin(); 64 | final float scale = range > 0f ? mDefaultProgress / (float) range : 0f; 65 | final int offset = (int) ((scale * availableWidth) + 0.5f); 66 | final int indicatorPosition = isLayoutRtl() && getMirrorForRtl() 67 | ? availableWidth - offset + mPaddingRight : offset + mPaddingLeft; 68 | 69 | final int saveCount = canvas.save(); 70 | canvas.translate(indicatorPosition, getHeight() / 2); 71 | defaultIndicator.draw(canvas); 72 | canvas.restoreToCount(saveCount); 73 | } 74 | } 75 | 76 | /** 77 | * N.B. This sets the default *unadjusted* progress, i.e. in the SeekBar's [0 - max] terms. 78 | */ 79 | public void setDefaultProgress(int defaultProgress) { 80 | if (mDefaultProgress != defaultProgress) { 81 | mDefaultProgress = defaultProgress; 82 | invalidate(); 83 | } 84 | } 85 | 86 | public int getDefaultProgress() { 87 | return mDefaultProgress; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/com/android/settings/widget/LabeledSeekBarPreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.settings.widget; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.graphics.drawable.Drawable; 22 | import android.util.AttributeSet; 23 | import android.widget.SeekBar; 24 | import android.widget.TextView; 25 | 26 | import androidx.core.content.res.TypedArrayUtils; 27 | import androidx.preference.PreferenceViewHolder; 28 | 29 | import org.protonaosp.elmyra.R; 30 | 31 | /** A slider preference with left and right labels **/ 32 | public class LabeledSeekBarPreference extends SeekBarPreference { 33 | 34 | private final int mTextStartId; 35 | private final int mTextEndId; 36 | private final int mTickMarkId; 37 | private OnPreferenceChangeListener mStopListener; 38 | 39 | public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, 40 | int defStyleRes) { 41 | 42 | super(context, attrs, defStyleAttr, defStyleRes); 43 | setLayoutResource(R.layout.preference_labeled_slider); 44 | 45 | final TypedArray styledAttrs = context.obtainStyledAttributes(attrs, 46 | R.styleable.LabeledSeekBarPreference); 47 | mTextStartId = styledAttrs.getResourceId( 48 | R.styleable.LabeledSeekBarPreference_textStart, 49 | R.string.summary_placeholder); 50 | mTextEndId = styledAttrs.getResourceId( 51 | R.styleable.LabeledSeekBarPreference_textEnd, 52 | R.string.summary_placeholder); 53 | mTickMarkId = styledAttrs.getResourceId( 54 | R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0); 55 | styledAttrs.recycle(); 56 | } 57 | 58 | public LabeledSeekBarPreference(Context context, AttributeSet attrs) { 59 | this(context, attrs, TypedArrayUtils.getAttr(context, 60 | androidx.preference.R.attr.seekBarPreferenceStyle, 61 | com.android.internal.R.attr.seekBarPreferenceStyle), 0); 62 | } 63 | 64 | @Override 65 | public void onBindViewHolder(PreferenceViewHolder holder) { 66 | super.onBindViewHolder(holder); 67 | 68 | final TextView startText = (TextView) holder.findViewById(android.R.id.text1); 69 | final TextView endText = (TextView) holder.findViewById(android.R.id.text2); 70 | startText.setText(mTextStartId); 71 | endText.setText(mTextEndId); 72 | 73 | if (mTickMarkId != 0) { 74 | final Drawable tickMark = getContext().getDrawable(mTickMarkId); 75 | final SeekBar seekBar = (SeekBar) holder.findViewById( 76 | com.android.internal.R.id.seekbar); 77 | seekBar.setTickMark(tickMark); 78 | } 79 | } 80 | 81 | public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) { 82 | mStopListener = listener; 83 | } 84 | 85 | @Override 86 | public void onStopTrackingTouch(SeekBar seekBar) { 87 | super.onStopTrackingTouch(seekBar); 88 | 89 | if (mStopListener != null) { 90 | mStopListener.onPreferenceChange(this, seekBar.getProgress()); 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/com/android/settings/widget/SeekBarPreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.settings.widget; 18 | 19 | import static android.view.HapticFeedbackConstants.CLOCK_TICK; 20 | 21 | import android.content.Context; 22 | import android.content.res.TypedArray; 23 | import android.os.Parcel; 24 | import android.os.Parcelable; 25 | import android.text.TextUtils; 26 | import android.util.AttributeSet; 27 | import android.view.KeyEvent; 28 | import android.view.View; 29 | import android.view.accessibility.AccessibilityNodeInfo; 30 | import android.widget.SeekBar; 31 | import android.widget.SeekBar.OnSeekBarChangeListener; 32 | 33 | import androidx.core.content.res.TypedArrayUtils; 34 | import androidx.preference.PreferenceViewHolder; 35 | 36 | import com.android.settingslib.RestrictedPreference; 37 | 38 | /** 39 | * Based on android.preference.SeekBarPreference, but uses support preference as base. 40 | */ 41 | public class SeekBarPreference extends RestrictedPreference 42 | implements OnSeekBarChangeListener, View.OnKeyListener { 43 | 44 | public static final int HAPTIC_FEEDBACK_MODE_NONE = 0; 45 | public static final int HAPTIC_FEEDBACK_MODE_ON_TICKS = 1; 46 | public static final int HAPTIC_FEEDBACK_MODE_ON_ENDS = 2; 47 | 48 | private int mProgress; 49 | private int mMax; 50 | private int mMin; 51 | private boolean mTrackingTouch; 52 | 53 | private boolean mContinuousUpdates; 54 | private int mHapticFeedbackMode = HAPTIC_FEEDBACK_MODE_NONE; 55 | private int mDefaultProgress = -1; 56 | 57 | private SeekBar mSeekBar; 58 | private boolean mShouldBlink; 59 | private int mAccessibilityRangeInfoType = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT; 60 | private CharSequence mOverrideSeekBarStateDescription; 61 | private CharSequence mSeekBarContentDescription; 62 | private CharSequence mSeekBarStateDescription; 63 | 64 | public SeekBarPreference( 65 | Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 66 | super(context, attrs, defStyleAttr, defStyleRes); 67 | 68 | TypedArray a = context.obtainStyledAttributes( 69 | attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes); 70 | setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax)); 71 | setMin(a.getInt(com.android.internal.R.styleable.ProgressBar_min, mMin)); 72 | a.recycle(); 73 | 74 | a = context.obtainStyledAttributes(attrs, 75 | com.android.internal.R.styleable.SeekBarPreference, defStyleAttr, defStyleRes); 76 | final int layoutResId = a.getResourceId( 77 | com.android.internal.R.styleable.SeekBarPreference_layout, 78 | com.android.internal.R.layout.preference_widget_seekbar); 79 | a.recycle(); 80 | 81 | a = context.obtainStyledAttributes( 82 | attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes); 83 | final boolean isSelectable = a.getBoolean( 84 | org.protonaosp.elmyra.R.styleable.Preference_android_selectable, false); 85 | setSelectable(isSelectable); 86 | a.recycle(); 87 | 88 | setLayoutResource(layoutResId); 89 | } 90 | 91 | public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { 92 | this(context, attrs, defStyleAttr, 0); 93 | } 94 | 95 | public SeekBarPreference(Context context, AttributeSet attrs) { 96 | this(context, attrs, TypedArrayUtils.getAttr(context, 97 | androidx.preference.R.attr.seekBarPreferenceStyle, 98 | com.android.internal.R.attr.seekBarPreferenceStyle)); 99 | } 100 | 101 | public SeekBarPreference(Context context) { 102 | this(context, null); 103 | } 104 | 105 | public void setShouldBlink(boolean shouldBlink) { 106 | mShouldBlink = shouldBlink; 107 | notifyChanged(); 108 | } 109 | 110 | @Override 111 | public boolean isSelectable() { 112 | if(isDisabledByAdmin()) { 113 | return true; 114 | } else { 115 | return super.isSelectable(); 116 | } 117 | } 118 | 119 | @Override 120 | public void onBindViewHolder(PreferenceViewHolder view) { 121 | super.onBindViewHolder(view); 122 | view.itemView.setOnKeyListener(this); 123 | mSeekBar = (SeekBar) view.findViewById( 124 | com.android.internal.R.id.seekbar); 125 | mSeekBar.setOnSeekBarChangeListener(this); 126 | mSeekBar.setMax(mMax); 127 | mSeekBar.setMin(mMin); 128 | mSeekBar.setProgress(mProgress); 129 | mSeekBar.setEnabled(isEnabled()); 130 | final CharSequence title = getTitle(); 131 | if (!TextUtils.isEmpty(mSeekBarContentDescription)) { 132 | mSeekBar.setContentDescription(mSeekBarContentDescription); 133 | } else if (!TextUtils.isEmpty(title)) { 134 | mSeekBar.setContentDescription(title); 135 | } 136 | if (!TextUtils.isEmpty(mSeekBarStateDescription)) { 137 | mSeekBar.setStateDescription(mSeekBarStateDescription); 138 | } 139 | if (mSeekBar instanceof DefaultIndicatorSeekBar) { 140 | ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress); 141 | } 142 | if (mShouldBlink) { 143 | View v = view.itemView; 144 | v.post(() -> { 145 | if (v.getBackground() != null) { 146 | final int centerX = v.getWidth() / 2; 147 | final int centerY = v.getHeight() / 2; 148 | v.getBackground().setHotspot(centerX, centerY); 149 | } 150 | v.setPressed(true); 151 | v.setPressed(false); 152 | mShouldBlink = false; 153 | }); 154 | } 155 | mSeekBar.setAccessibilityDelegate(new View.AccessibilityDelegate() { 156 | @Override 157 | public void onInitializeAccessibilityNodeInfo(View view, AccessibilityNodeInfo info) { 158 | super.onInitializeAccessibilityNodeInfo(view, info); 159 | // Update the range info with the correct type 160 | AccessibilityNodeInfo.RangeInfo rangeInfo = info.getRangeInfo(); 161 | if (rangeInfo != null) { 162 | info.setRangeInfo(AccessibilityNodeInfo.RangeInfo.obtain( 163 | mAccessibilityRangeInfoType, rangeInfo.getMin(), 164 | rangeInfo.getMax(), rangeInfo.getCurrent())); 165 | } 166 | if (mOverrideSeekBarStateDescription != null) { 167 | info.setStateDescription(mOverrideSeekBarStateDescription); 168 | } 169 | } 170 | }); 171 | } 172 | 173 | @Override 174 | public CharSequence getSummary() { 175 | return null; 176 | } 177 | 178 | @Override 179 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 180 | setProgress(restoreValue ? getPersistedInt(mProgress) 181 | : (Integer) defaultValue); 182 | } 183 | 184 | @Override 185 | protected Object onGetDefaultValue(TypedArray a, int index) { 186 | return a.getInt(index, 0); 187 | } 188 | 189 | @Override 190 | public boolean onKey(View v, int keyCode, KeyEvent event) { 191 | if (event.getAction() != KeyEvent.ACTION_DOWN) { 192 | return false; 193 | } 194 | 195 | SeekBar seekBar = (SeekBar) v.findViewById(com.android.internal.R.id.seekbar); 196 | if (seekBar == null) { 197 | return false; 198 | } 199 | return seekBar.onKeyDown(keyCode, event); 200 | } 201 | 202 | public void setMax(int max) { 203 | if (max != mMax) { 204 | mMax = max; 205 | notifyChanged(); 206 | } 207 | } 208 | 209 | public void setMin(int min) { 210 | if (min != mMin) { 211 | mMin = min; 212 | notifyChanged(); 213 | } 214 | } 215 | 216 | public int getMax() { 217 | return mMax; 218 | } 219 | 220 | public int getMin() { 221 | return mMin; 222 | } 223 | 224 | public void setProgress(int progress) { 225 | setProgress(progress, true); 226 | } 227 | 228 | /** 229 | * Sets the progress point to draw a single tick mark representing a default value. 230 | */ 231 | public void setDefaultProgress(int defaultProgress) { 232 | if (mDefaultProgress != defaultProgress) { 233 | mDefaultProgress = defaultProgress; 234 | if (mSeekBar instanceof DefaultIndicatorSeekBar) { 235 | ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress); 236 | } 237 | } 238 | } 239 | 240 | /** 241 | * When {@code continuousUpdates} is true, update the persisted setting immediately as the thumb 242 | * is dragged along the SeekBar. Otherwise, only update the value of the setting when the thumb 243 | * is dropped. 244 | */ 245 | public void setContinuousUpdates(boolean continuousUpdates) { 246 | mContinuousUpdates = continuousUpdates; 247 | } 248 | 249 | /** 250 | * Sets the haptic feedback mode. HAPTIC_FEEDBACK_MODE_ON_TICKS means to perform haptic feedback 251 | * as the SeekBar's progress is updated; HAPTIC_FEEDBACK_MODE_ON_ENDS means to perform haptic 252 | * feedback as the SeekBar's progress value is equal to the min/max value. 253 | * 254 | * @param hapticFeedbackMode the haptic feedback mode. 255 | */ 256 | public void setHapticFeedbackMode(int hapticFeedbackMode) { 257 | mHapticFeedbackMode = hapticFeedbackMode; 258 | } 259 | 260 | private void setProgress(int progress, boolean notifyChanged) { 261 | if (progress > mMax) { 262 | progress = mMax; 263 | } 264 | if (progress < mMin) { 265 | progress = mMin; 266 | } 267 | if (progress != mProgress) { 268 | mProgress = progress; 269 | persistInt(progress); 270 | if (notifyChanged) { 271 | notifyChanged(); 272 | } 273 | } 274 | } 275 | 276 | public int getProgress() { 277 | return mProgress; 278 | } 279 | 280 | /** 281 | * Persist the seekBar's progress value if callChangeListener 282 | * returns true, otherwise set the seekBar's progress to the stored value 283 | */ 284 | void syncProgress(SeekBar seekBar) { 285 | int progress = seekBar.getProgress(); 286 | if (progress != mProgress) { 287 | if (callChangeListener(progress)) { 288 | setProgress(progress, false); 289 | switch (mHapticFeedbackMode) { 290 | case HAPTIC_FEEDBACK_MODE_ON_TICKS: 291 | seekBar.performHapticFeedback(CLOCK_TICK); 292 | break; 293 | case HAPTIC_FEEDBACK_MODE_ON_ENDS: 294 | if (progress == mMax || progress == mMin) { 295 | seekBar.performHapticFeedback(CLOCK_TICK); 296 | } 297 | break; 298 | } 299 | } else { 300 | seekBar.setProgress(mProgress); 301 | } 302 | } 303 | } 304 | 305 | @Override 306 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 307 | if (fromUser && (mContinuousUpdates || !mTrackingTouch)) { 308 | syncProgress(seekBar); 309 | } 310 | } 311 | 312 | @Override 313 | public void onStartTrackingTouch(SeekBar seekBar) { 314 | mTrackingTouch = true; 315 | } 316 | 317 | @Override 318 | public void onStopTrackingTouch(SeekBar seekBar) { 319 | mTrackingTouch = false; 320 | if (seekBar.getProgress() != mProgress) { 321 | syncProgress(seekBar); 322 | } 323 | } 324 | 325 | /** 326 | * Specify the type of range this seek bar represents. 327 | * 328 | * @param rangeInfoType The type of range to be shared with accessibility 329 | * 330 | * @see android.view.accessibility.AccessibilityNodeInfo.RangeInfo 331 | */ 332 | public void setAccessibilityRangeInfoType(int rangeInfoType) { 333 | mAccessibilityRangeInfoType = rangeInfoType; 334 | } 335 | 336 | public void setSeekBarContentDescription(CharSequence contentDescription) { 337 | mSeekBarContentDescription = contentDescription; 338 | if (mSeekBar != null) { 339 | mSeekBar.setContentDescription(contentDescription); 340 | } 341 | } 342 | 343 | /** 344 | * Specify the state description for this seek bar represents. 345 | * 346 | * @param stateDescription the state description of seek bar 347 | */ 348 | public void setSeekBarStateDescription(CharSequence stateDescription) { 349 | mSeekBarStateDescription = stateDescription; 350 | if (mSeekBar != null) { 351 | mSeekBar.setStateDescription(stateDescription); 352 | } 353 | } 354 | 355 | /** 356 | * Overrides the state description of {@link SeekBar} with given content. 357 | */ 358 | public void overrideSeekBarStateDescription(CharSequence stateDescription) { 359 | mOverrideSeekBarStateDescription = stateDescription; 360 | } 361 | 362 | @Override 363 | protected Parcelable onSaveInstanceState() { 364 | /* 365 | * Suppose a client uses this preference type without persisting. We 366 | * must save the instance state so it is able to, for example, survive 367 | * orientation changes. 368 | */ 369 | 370 | final Parcelable superState = super.onSaveInstanceState(); 371 | if (isPersistent()) { 372 | // No need to save instance state since it's persistent 373 | return superState; 374 | } 375 | 376 | // Save the instance state 377 | final SavedState myState = new SavedState(superState); 378 | myState.progress = mProgress; 379 | myState.max = mMax; 380 | myState.min = mMin; 381 | return myState; 382 | } 383 | 384 | @Override 385 | protected void onRestoreInstanceState(Parcelable state) { 386 | if (!state.getClass().equals(SavedState.class)) { 387 | // Didn't save state for us in onSaveInstanceState 388 | super.onRestoreInstanceState(state); 389 | return; 390 | } 391 | 392 | // Restore the instance state 393 | SavedState myState = (SavedState) state; 394 | super.onRestoreInstanceState(myState.getSuperState()); 395 | mProgress = myState.progress; 396 | mMax = myState.max; 397 | mMin = myState.min; 398 | notifyChanged(); 399 | } 400 | 401 | /** 402 | * SavedState, a subclass of {@link BaseSavedState}, will store the state 403 | * of MyPreference, a subclass of Preference. 404 | *

405 | * It is important to always call through to super methods. 406 | */ 407 | private static class SavedState extends BaseSavedState { 408 | int progress; 409 | int max; 410 | int min; 411 | 412 | public SavedState(Parcel source) { 413 | super(source); 414 | 415 | // Restore the click counter 416 | progress = source.readInt(); 417 | max = source.readInt(); 418 | min = source.readInt(); 419 | } 420 | 421 | @Override 422 | public void writeToParcel(Parcel dest, int flags) { 423 | super.writeToParcel(dest, flags); 424 | 425 | // Save the click counter 426 | dest.writeInt(progress); 427 | dest.writeInt(max); 428 | dest.writeInt(min); 429 | } 430 | 431 | public SavedState(Parcelable superState) { 432 | super(superState); 433 | } 434 | 435 | @SuppressWarnings("unused") 436 | public static final Parcelable.Creator CREATOR = 437 | new Parcelable.Creator() { 438 | public SavedState createFromParcel(Parcel in) { 439 | return new SavedState(in); 440 | } 441 | 442 | public SavedState[] newArray(int size) { 443 | return new SavedState[size]; 444 | } 445 | }; 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/BootReceiver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra 18 | 19 | import android.content.BroadcastReceiver 20 | import android.content.Context 21 | import android.content.Intent 22 | 23 | class BootReceiver : BroadcastReceiver() { 24 | override fun onReceive(context: Context?, intent: Intent?) { 25 | Intent(context ?: return, ElmyraService::class.java).also { 26 | context.startService(it) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra 18 | 19 | const val TAG = "Elmyra/Service" 20 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/ElmyraService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra 18 | 19 | import android.app.Service 20 | import android.content.BroadcastReceiver 21 | import android.content.Context 22 | import android.content.Intent 23 | import android.content.SharedPreferences 24 | import android.content.IntentFilter 25 | import android.hardware.location.ContextHubClient 26 | import android.hardware.location.ContextHubClientCallback 27 | import android.hardware.location.ContextHubManager 28 | import android.hardware.location.NanoAppMessage 29 | import android.os.SystemClock 30 | import android.os.VibrationEffect 31 | import android.os.Vibrator 32 | import android.util.Log 33 | import androidx.preference.PreferenceManager 34 | 35 | import com.google.protobuf.nano.MessageNano 36 | 37 | import org.protonaosp.elmyra.actions.* 38 | import org.protonaosp.elmyra.proto.nano.ContextHubMessages 39 | import org.protonaosp.elmyra.TAG 40 | import org.protonaosp.elmyra.getDePrefs 41 | import org.protonaosp.elmyra.getAction 42 | import org.protonaosp.elmyra.getSensitivity 43 | import org.protonaosp.elmyra.getEnabled 44 | import org.protonaosp.elmyra.getAllowScreenOff 45 | 46 | private const val NANOAPP_ID = 0x476f6f676c00100eL 47 | private const val REJECT_COOLDOWN_TIME = 1000 // ms 48 | 49 | class ElmyraService : Service(), SharedPreferences.OnSharedPreferenceChangeListener { 50 | // Services 51 | private lateinit var vibrator: Vibrator 52 | private lateinit var prefs: SharedPreferences 53 | private lateinit var action: Action 54 | private lateinit var client: ContextHubClient 55 | 56 | // State 57 | private var inGesture = false 58 | private var screenRegistered = false 59 | private var lastRejectTime = 0L - REJECT_COOLDOWN_TIME 60 | 61 | // Settings 62 | private var enabled = true 63 | private var sensitivity = 0.5f 64 | set(value) { 65 | field = value 66 | if (enabled) { 67 | sendNewSensitivity() 68 | } 69 | } 70 | 71 | override fun onBind(intent: Intent?) = null 72 | 73 | override fun onCreate() { 74 | vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator 75 | prefs = getDePrefs() 76 | 77 | Log.i(TAG, "Initializing CHRE gesture") 78 | 79 | val manager = getSystemService("contexthub") as ContextHubManager 80 | client = manager.createClient(manager.contextHubs[0], chreCallback) 81 | 82 | updateAction() 83 | updateSensitivity() 84 | updateEnabled() 85 | updateScreenCallback() 86 | 87 | // Only register for changes after initial pref updates 88 | prefs.registerOnSharedPreferenceChangeListener(this) 89 | } 90 | 91 | override fun onDestroy() { 92 | prefs.unregisterOnSharedPreferenceChangeListener(this) 93 | unregisterReceiver(screenCallback) 94 | } 95 | 96 | private fun createAction(key: String): Action { 97 | return when (key) { 98 | "screenshot" -> ScreenshotAction(this) 99 | "assistant" -> AssistantAction(this) 100 | "camera" -> CameraAction(this) 101 | "power_menu" -> PowerMenuAction(this) 102 | "mute" -> MuteAction(this) 103 | "flashlight" -> FlashlightAction(this) 104 | "screen" -> ScreenAction(this) 105 | 106 | else -> DummyAction(this) 107 | } 108 | } 109 | 110 | private fun updateSensitivity() { 111 | sensitivity = prefs.getSensitivity(this) / 10f 112 | Log.i(TAG, "Setting sensitivity to $sensitivity") 113 | } 114 | 115 | private fun updateAction() { 116 | val key = prefs.getAction(this) 117 | Log.i(TAG, "Setting action to $key") 118 | action = createAction(key) 119 | 120 | // For settings 121 | prefs.edit().putBoolean(getString(R.string.pref_key_allow_screen_off_action_forced), 122 | !action.canRunWhenScreenOff()).commit() 123 | } 124 | 125 | private fun updateEnabled() { 126 | enabled = prefs.getEnabled(this) 127 | if (enabled) { 128 | Log.i(TAG, "Enabling gesture by pref") 129 | enableGesture() 130 | } else { 131 | Log.i(TAG, "Disabling gesture by pref") 132 | disableGesture() 133 | } 134 | } 135 | 136 | private fun updateScreenCallback() { 137 | val allowScreenOff = prefs.getAllowScreenOff(this) 138 | 139 | // Listen if either condition *can't* run when screen is off 140 | if (!allowScreenOff || !action.canRunWhenScreenOff()) { 141 | val filter = IntentFilter().apply { 142 | addAction(Intent.ACTION_SCREEN_ON) 143 | addAction(Intent.ACTION_SCREEN_OFF) 144 | } 145 | 146 | if (!screenRegistered) { 147 | Log.i(TAG, "Listening to screen on/off events") 148 | registerReceiver(screenCallback, filter) 149 | screenRegistered = true 150 | } 151 | } else if (screenRegistered) { 152 | Log.i(TAG, "Stopped listening to screen on/off events") 153 | unregisterReceiver(screenCallback) 154 | screenRegistered = false 155 | } 156 | } 157 | 158 | override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { 159 | when (key) { 160 | getString(R.string.pref_key_enabled) -> updateEnabled() 161 | getString(R.string.pref_key_sensitivity) -> updateSensitivity() 162 | // Action might change screen callback behavior 163 | getString(R.string.pref_key_action) -> { 164 | updateAction() 165 | updateScreenCallback() 166 | } 167 | getString(R.string.pref_key_allow_screen_off) -> updateScreenCallback() 168 | } 169 | } 170 | 171 | private fun enableGesture() { 172 | val msg = ContextHubMessages.RecognizerStart() 173 | // Only report events to AP if gesture is halfway done 174 | msg.progressReportThreshold = 0.5f 175 | msg.sensitivity = sensitivity 176 | 177 | sendNanoappMsg(MessageType.RECOGNIZER_START.id, MessageNano.toByteArray(msg)) 178 | } 179 | 180 | private fun disableGesture() { 181 | sendNanoappMsg(MessageType.RECOGNIZER_STOP.id, ByteArray(0)) 182 | } 183 | 184 | private fun sendNewSensitivity() { 185 | val msg = ContextHubMessages.SensitivityUpdate() 186 | msg.sensitivity = sensitivity 187 | sendNanoappMsg(MessageType.SENSITIVITY_UPDATE.id, MessageNano.toByteArray(msg)) 188 | } 189 | 190 | private fun sendNanoappMsg(msgType: Int, bytes: ByteArray) { 191 | val message = NanoAppMessage.createMessageToNanoApp(NANOAPP_ID, msgType, bytes) 192 | val ret = client.sendMessageToNanoApp(message) 193 | if (ret != 0) { 194 | Log.e(TAG, "Failed to send message of type $msgType to nanoapp: $ret") 195 | } 196 | } 197 | 198 | private fun onGestureDetected(msg: ContextHubMessages.GestureDetected) { 199 | Log.i(TAG, "Gesture detected hostSuspended=${msg.hostSuspended} hapticConsumed=${msg.hapticConsumed}") 200 | 201 | if (action.canRun()) { 202 | if (!msg.hapticConsumed) { 203 | vibrator.vibrate(vibEdgeRelease) 204 | } 205 | 206 | action.run() 207 | inGesture = false 208 | } 209 | } 210 | 211 | private fun onGestureProgress(msg: ContextHubMessages.GestureProgress) { 212 | // Ignore beginning and end points 213 | if (msg.progress < 0.49f || msg.progress > 0.99f) { 214 | inGesture = false 215 | } else if (!inGesture) { 216 | if (action.canRun()) { 217 | // Enter gesture and vibrate to indicate that 218 | inGesture = true 219 | vibrator.vibrate(vibEdgeSqueeze) 220 | } else { 221 | val now = SystemClock.elapsedRealtime() 222 | if (now - lastRejectTime >= REJECT_COOLDOWN_TIME) { 223 | vibrator.vibrate(vibReject) 224 | lastRejectTime = now 225 | } 226 | } 227 | } 228 | } 229 | 230 | private val chreCallback = object : ContextHubClientCallback() { 231 | override fun onMessageFromNanoApp(client: ContextHubClient, msg: NanoAppMessage) { 232 | // Ignore other nanoapps 233 | if (msg.nanoAppId != NANOAPP_ID) { 234 | return 235 | } 236 | 237 | when (msg.messageType) { 238 | MessageType.GESTURE_DETECTED.id -> { 239 | val detectedMsg = ContextHubMessages.GestureDetected.parseFrom(msg.messageBody) 240 | onGestureDetected(detectedMsg) 241 | } 242 | MessageType.GESTURE_PROGRESS.id -> { 243 | val progressMsg = ContextHubMessages.GestureProgress.parseFrom(msg.messageBody) 244 | onGestureProgress(progressMsg) 245 | } 246 | 247 | // Fallback for other unexpected messages 248 | else -> Log.w(TAG, "Received unknown message of type ${msg.messageType}: $msg") 249 | } 250 | } 251 | 252 | override fun onNanoAppAborted(client: ContextHubClient, nanoappId: Long, error: Int) { 253 | if (nanoappId == NANOAPP_ID) { 254 | Log.e(TAG, "Elmyra CHRE nanoapp aborted: $error") 255 | } 256 | } 257 | } 258 | 259 | private val screenCallback = object : BroadcastReceiver() { 260 | override fun onReceive(context: Context?, intent: Intent?) { 261 | if (enabled) { 262 | when (intent?.action) { 263 | Intent.ACTION_SCREEN_ON -> { 264 | Log.i(TAG, "Enabling gesture due to screen on") 265 | enableGesture() 266 | } 267 | // Disable gesture entirely to save power 268 | Intent.ACTION_SCREEN_OFF -> { 269 | Log.i(TAG, "Disabling gesture due to screen off") 270 | disableGesture() 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | companion object { 278 | // Vibration effects from HapticFeedbackConstants 279 | // Duplicated because we can't use performHapticFeedback in a background service 280 | private val vibEdgeRelease = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) 281 | private val vibEdgeSqueeze = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK) 282 | private val vibReject = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/MessageType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra 18 | 19 | enum class MessageType(val id: Int) { 20 | RECOGNIZER_START(200), 21 | RECOGNIZER_STOP(201), 22 | SENSITIVITY_UPDATE(202), 23 | SNAPSHOT_REQUEST(203), 24 | CHASSIS_REQUEST(204), 25 | GRAB_RECOGNIZER_START(205), 26 | GRAB_RECOGNIZER_STOP(206), 27 | GESTURE_PROGRESS(300), 28 | GESTURE_DETECTED(301), 29 | SNAPSHOT_RESPONSE(302), 30 | CHASSIS_RESPONSE(303), 31 | GRAB_DETECTED(304), 32 | GRAB_RELEASED(305), 33 | } 34 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/Preferences.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra 18 | 19 | import android.content.Context 20 | import android.content.SharedPreferences 21 | 22 | const val PREFS_NAME = "elmyra_preferences" 23 | 24 | fun Context.getDePrefs(): SharedPreferences { 25 | return createDeviceProtectedStorageContext() 26 | .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 27 | } 28 | 29 | fun SharedPreferences.getEnabled(context: Context): Boolean { 30 | return getBoolean(context.getString(R.string.pref_key_enabled), 31 | context.resources.getBoolean(R.bool.default_enabled)) 32 | } 33 | 34 | fun SharedPreferences.getAction(context: Context): String { 35 | return getString(context.getString(R.string.pref_key_action), 36 | context.getString(R.string.default_action)) 37 | } 38 | 39 | fun SharedPreferences.getAllowScreenOff(context: Context): Boolean { 40 | return getBoolean(context.getString(R.string.pref_key_allow_screen_off), 41 | context.resources.getBoolean(R.bool.default_allow_screen_off)) 42 | } 43 | 44 | fun SharedPreferences.getSensitivity(context: Context): Int { 45 | return getInt(context.getString(R.string.pref_key_sensitivity), 46 | context.resources.getInteger(R.integer.default_sensitivity)) 47 | } 48 | 49 | fun SharedPreferences.getActionName(context: Context): String { 50 | val actionNames = context.resources.getStringArray(R.array.action_names) 51 | val actionValues = context.resources.getStringArray(R.array.action_values) 52 | return actionNames[actionValues.indexOf(getAction(context))] 53 | } 54 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/Action.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | 21 | abstract class Action(val context: Context) { 22 | open fun canRun() = true 23 | open fun canRunWhenScreenOff() = true 24 | abstract fun run() 25 | 26 | open fun destroy() {} 27 | } 28 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/AssistantAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.app.StatusBarManager 20 | import android.content.Context 21 | import android.os.Bundle 22 | import android.os.ServiceManager 23 | 24 | import com.android.internal.statusbar.IStatusBarService 25 | 26 | class AssistantAction(context: Context) : Action(context) { 27 | val service = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE)) 28 | 29 | override fun run() { 30 | service.startAssist(Bundle()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/CameraAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.app.StatusBarManager 20 | import android.content.Context 21 | import android.content.Intent 22 | import android.os.PowerManager 23 | import android.os.SystemClock 24 | import android.provider.MediaStore 25 | 26 | class CameraAction(context: Context) : Action(context) { 27 | val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager 28 | 29 | override fun run() { 30 | if (!pm.isInteractive()) { 31 | pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, 32 | "org.protonaosp.elmyra:GESTURE") 33 | } 34 | 35 | val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE).apply { 36 | addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or 37 | Intent.FLAG_ACTIVITY_NEW_TASK) 38 | } 39 | context.startActivity(intent) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/DummyAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | 21 | class DummyAction(context: Context) : Action(context) { 22 | override fun run() {} 23 | } 24 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/FlashlightAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | import android.os.Handler 21 | import android.os.Looper 22 | import android.hardware.camera2.CameraAccessException 23 | import android.hardware.camera2.CameraCharacteristics 24 | import android.hardware.camera2.CameraManager 25 | import android.util.Log 26 | 27 | import org.protonaosp.elmyra.TAG 28 | 29 | class FlashlightAction(context: Context) : Action(context) { 30 | private val handler = Handler(Looper.getMainLooper()) 31 | private val cm = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager 32 | private val torchCamId = findCamera() 33 | private var available = true 34 | private var enabled = false 35 | 36 | private val torchCallback = object : CameraManager.TorchCallback() { 37 | override fun onTorchModeUnavailable(cameraId: String) { 38 | if (cameraId == torchCamId) { 39 | available = false 40 | } 41 | } 42 | 43 | override fun onTorchModeChanged(cameraId: String, newEnabled: Boolean) { 44 | if (cameraId == torchCamId) { 45 | available = true 46 | enabled = newEnabled 47 | } 48 | } 49 | } 50 | 51 | init { 52 | if (torchCamId != null) { 53 | cm.registerTorchCallback(torchCallback, handler) 54 | } 55 | } 56 | 57 | override fun canRun() = torchCamId != null && available 58 | 59 | override fun run() { 60 | try { 61 | cm.setTorchMode(torchCamId, !enabled) 62 | } catch (e: CameraAccessException) { 63 | Log.e(TAG, "Failed to set torch mode to $enabled", e) 64 | return 65 | } 66 | 67 | enabled = !enabled 68 | } 69 | 70 | override fun destroy() { 71 | if (torchCamId != null) { 72 | cm.unregisterTorchCallback(torchCallback) 73 | } 74 | } 75 | 76 | private fun findCamera(): String? { 77 | for (id in cm.cameraIdList) { 78 | val characteristics = cm.getCameraCharacteristics(id) 79 | val flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) 80 | val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING) 81 | 82 | if (flashAvailable != null && flashAvailable && lensFacing != null && 83 | lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 84 | return id 85 | } 86 | } 87 | 88 | return null 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/MuteAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | import android.media.AudioManager 21 | import android.os.UserHandle 22 | import android.provider.Settings 23 | import android.widget.Toast 24 | 25 | class MuteAction(context: Context) : Action(context) { 26 | val service = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager 27 | 28 | override fun canRun() = context.resources.getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled) 29 | 30 | override fun run() { 31 | // We can't call AudioService#silenceRingerModeInternal from here, so this is a partial copy of it 32 | var silenceRingerSetting = Settings.Secure.getIntForUser(context.contentResolver, 33 | Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF, 34 | UserHandle.USER_CURRENT) 35 | 36 | var ringerMode: Int 37 | var toastText: Int 38 | when (silenceRingerSetting) { 39 | Settings.Secure.VOLUME_HUSH_VIBRATE -> { 40 | ringerMode = AudioManager.RINGER_MODE_VIBRATE 41 | toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate 42 | } 43 | // VOLUME_HUSH_MUTE and VOLUME_HUSH_OFF 44 | else -> { 45 | ringerMode = AudioManager.RINGER_MODE_SILENT 46 | toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_silent 47 | } 48 | } 49 | 50 | service.setRingerModeInternal(ringerMode) 51 | Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/PowerMenuAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | import android.os.PowerManager 21 | import android.os.SystemClock 22 | import android.view.IWindowManager 23 | import android.view.WindowManagerGlobal 24 | 25 | class PowerMenuAction(context: Context) : Action(context) { 26 | val wm = WindowManagerGlobal.getWindowManagerService() 27 | val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager 28 | 29 | override fun run() { 30 | if (!pm.isInteractive()) { 31 | pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "org.protonaosp.elmyra:GESTURE") 32 | } 33 | 34 | wm.showGlobalActions() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/ScreenAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | import android.os.PowerManager 21 | import android.os.SystemClock 22 | 23 | class ScreenAction(context: Context) : Action(context) { 24 | val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager 25 | 26 | override fun run() { 27 | if (pm.isInteractive()) { 28 | pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0) 29 | } else { 30 | pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "org.protonaosp.elmyra:GESTURE") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/actions/ScreenshotAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.actions 18 | 19 | import android.content.Context 20 | import android.os.Handler 21 | import android.os.Looper 22 | import android.os.PowerManager 23 | import android.view.WindowManager 24 | 25 | import com.android.internal.util.ScreenshotHelper 26 | 27 | class ScreenshotAction(context: Context) : Action(context) { 28 | val helper = ScreenshotHelper(context) 29 | private val handler = Handler(Looper.getMainLooper()) 30 | val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager 31 | 32 | override fun canRun() = pm.isInteractive() 33 | override fun canRunWhenScreenOff() = false 34 | 35 | override fun run() { 36 | helper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, 37 | true, true, WindowManager.ScreenshotSource.SCREENSHOT_OTHER, 38 | handler, null) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/proto/chre_messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.protonaosp.elmyra.proto; 4 | 5 | option java_outer_classname = "ContextHubMessages"; 6 | option java_package = "org.protonaosp.elmyra.proto"; 7 | 8 | message RecognizerStart { 9 | // Minimum gesture progress threshold for reporting events to AP 10 | float progress_report_threshold = 1; 11 | // Gesture trigger sensitivty (1.0 scale) 12 | float sensitivity = 2; 13 | } 14 | 15 | message SensitivityUpdate { 16 | // Gesture trigger sensitivty (1.0 scale) 17 | float sensitivity = 1; 18 | } 19 | 20 | message GestureProgress { 21 | // Progress towards finished gesture (GestureDetected event), 0.0-1.0 22 | float progress = 1; 23 | } 24 | 25 | message GestureDetected { 26 | // Was AP suspended when the gesture was finished? 27 | // If so, we didn't get any progress events for this gesture 28 | bool host_suspended = 1; 29 | // Did SLPI perform the vibration or does the AP need to do it? 30 | bool haptic_consumed = 2; 31 | } 32 | 33 | enum MessageType { 34 | // Dummy value to make protobuf happy 35 | UNDEFINED = 0; 36 | 37 | /* 38 | * From AP HALs 39 | */ 40 | 41 | NANOAPP_LOADED = 1; 42 | // napp_header values loaded? 43 | NANOAPP_CONFIG = 100; 44 | 45 | /* 46 | * AP -> SLPI 47 | */ 48 | 49 | // Enable gesture with specified sensitivity 50 | RECOGNIZER_START = 200; 51 | // Disable gesture 52 | RECOGNIZER_STOP = 201; 53 | // Set new sensitivity without restarting gesture 54 | SENSITIVITY_UPDATE = 202; 55 | // For debugging: replies with recent raw sensor data 56 | SNAPSHOT_REQUEST = 203; 57 | // For debugging: replies with sensor chassis calibration 58 | CHASSIS_REQUEST = 204; 59 | // Abandoned grab gesture? 60 | GRAB_RECOGNIZER_START = 205; 61 | // Abandoned grab gesture? 62 | GRAB_RECOGNIZER_STOP = 206; 63 | 64 | /* 65 | * SLPI -> AP 66 | */ 67 | 68 | // Squeeze is in progress and above report threshold sent in RECOGNIZER_START 69 | GESTURE_PROGRESS = 300; 70 | // Squeeze released (SLPI may or may not have vibrated) 71 | GESTURE_DETECTED = 301; 72 | // Recent raw sensor data for debugging 73 | SNAPSHOT_RESPONSE = 302; 74 | // Sensor calibration values for device chassis 75 | CHASSIS_RESPONSE = 303; 76 | // Corresponds to GRAB_RECOGNIZER_START 77 | GRAB_DETECTED = 304; 78 | // Corresponds to GRAB_RECOGNIZER_STOP 79 | GRAB_RELEASED = 305; 80 | } 81 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/settings/SearchProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.settings 18 | 19 | import android.content.Intent 20 | import android.database.Cursor 21 | import android.database.MatrixCursor 22 | import android.provider.SearchIndexablesProvider 23 | import android.provider.SearchIndexablesContract.* 24 | 25 | import org.protonaosp.elmyra.R 26 | 27 | class SearchProvider : SearchIndexablesProvider() { 28 | override fun onCreate() = true 29 | override fun queryXmlResources(projection: Array?) = MatrixCursor(INDEXABLES_XML_RES_COLUMNS) 30 | override fun queryNonIndexableKeys(projection: Array?) = MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS) 31 | 32 | override fun queryRawData(projection: Array?): Cursor { 33 | val ref = Array(INDEXABLES_RAW_COLUMNS.size) { null } 34 | ref[COLUMN_INDEX_RAW_KEY] = context.getString(R.string.settings_entry_title) 35 | ref[COLUMN_INDEX_RAW_TITLE] = context.getString(R.string.settings_entry_title) 36 | ref[COLUMN_INDEX_RAW_SUMMARY_ON] = context.getString(R.string.setting_enabled_summary) 37 | ref[COLUMN_INDEX_RAW_KEYWORDS] = context.getString(R.string.settings_search_keywords) 38 | 39 | // For breadcrumb generation 40 | ref[COLUMN_INDEX_RAW_SCREEN_TITLE] = context.getString(R.string.settings_entry_title) 41 | ref[COLUMN_INDEX_RAW_CLASS_NAME] = SettingsActivity::class.java.name 42 | 43 | ref[COLUMN_INDEX_RAW_INTENT_ACTION] = Intent.ACTION_MAIN 44 | ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = context.applicationInfo.packageName 45 | ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = SettingsActivity::class.java.name 46 | 47 | return MatrixCursor(INDEXABLES_RAW_COLUMNS).apply { 48 | addRow(ref) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.settings 18 | 19 | import android.os.Bundle 20 | 21 | import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity 22 | 23 | import org.protonaosp.elmyra.R 24 | 25 | class SettingsActivity : CollapsingToolbarBaseActivity() { 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.settings_activity) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/settings/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.settings 18 | 19 | import android.os.Bundle 20 | import android.content.SharedPreferences 21 | import androidx.preference.PreferenceFragmentCompat 22 | import androidx.preference.PreferenceManager 23 | import androidx.preference.ListPreference 24 | import androidx.preference.SwitchPreference 25 | import com.android.settings.widget.LabeledSeekBarPreference 26 | import com.android.settings.widget.SeekBarPreference 27 | 28 | import org.protonaosp.elmyra.R 29 | import org.protonaosp.elmyra.getDePrefs 30 | import org.protonaosp.elmyra.PREFS_NAME 31 | import org.protonaosp.elmyra.getEnabled 32 | import org.protonaosp.elmyra.getSensitivity 33 | import org.protonaosp.elmyra.getAction 34 | import org.protonaosp.elmyra.getActionName 35 | import org.protonaosp.elmyra.getAllowScreenOff 36 | 37 | class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { 38 | private lateinit var prefs: SharedPreferences 39 | 40 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 41 | setPreferencesFromResource(R.xml.settings, rootKey) 42 | } 43 | 44 | override fun onCreate(savedInstanceState: Bundle?) { 45 | super.onCreate(savedInstanceState) 46 | preferenceManager.setStorageDeviceProtected() 47 | preferenceManager.sharedPreferencesName = PREFS_NAME 48 | 49 | prefs = context!!.getDePrefs() 50 | prefs.registerOnSharedPreferenceChangeListener(this) 51 | updateUi() 52 | } 53 | 54 | override fun onDestroy() { 55 | super.onDestroy() 56 | prefs.unregisterOnSharedPreferenceChangeListener(this) 57 | } 58 | 59 | override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { 60 | updateUi() 61 | } 62 | 63 | private fun updateUi() { 64 | // Enabled 65 | findPreference(getString(R.string.pref_key_enabled))?.apply { 66 | setChecked(prefs.getEnabled(context)) 67 | } 68 | 69 | // Sensitivity value 70 | findPreference(getString(R.string.pref_key_sensitivity))?.apply { 71 | progress = prefs.getSensitivity(context) 72 | setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS) 73 | } 74 | 75 | // Action value and summary 76 | findPreference(getString(R.string.pref_key_action))?.apply { 77 | value = prefs.getAction(context) 78 | summary = prefs.getActionName(context) 79 | } 80 | 81 | // Screen state based on action 82 | findPreference(getString(R.string.pref_key_allow_screen_off))?.apply { 83 | val screenForced = prefs.getBoolean(getString(R.string.pref_key_allow_screen_off_action_forced), false) 84 | setEnabled(!screenForced) 85 | if (screenForced) { 86 | setSummary(getString(R.string.setting_screen_off_blocked_summary)) 87 | setPersistent(false) 88 | setChecked(false) 89 | } else { 90 | setSummary(getString(R.string.setting_screen_off_summary)) 91 | setPersistent(true) 92 | setChecked(prefs.getAllowScreenOff(context)) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/settings/SummaryProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.settings 18 | 19 | import android.content.ContentProvider 20 | import android.content.ContentValues 21 | import android.content.SharedPreferences 22 | import android.database.Cursor 23 | import android.net.Uri 24 | import android.os.Bundle 25 | import com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY 26 | 27 | import org.protonaosp.elmyra.R 28 | import org.protonaosp.elmyra.getDePrefs 29 | import org.protonaosp.elmyra.getEnabled 30 | import org.protonaosp.elmyra.getActionName 31 | 32 | class SummaryProvider : ContentProvider() { 33 | private lateinit var prefs: SharedPreferences 34 | 35 | override fun onCreate(): Boolean { 36 | prefs = context.getDePrefs() 37 | return true 38 | } 39 | 40 | override fun call(method: String, uri: String, extras: Bundle?): Bundle { 41 | val bundle = Bundle() 42 | val summary = when (method) { 43 | "entry" -> if (prefs.getEnabled(context)) { 44 | context.getString(R.string.settings_entry_summary_on, prefs.getActionName(context)) 45 | } else { 46 | context.getString(R.string.settings_entry_summary_off) 47 | } 48 | else -> throw IllegalArgumentException("Unknown method: $method") 49 | } 50 | 51 | bundle.putString(META_DATA_PREFERENCE_SUMMARY, summary) 52 | return bundle 53 | } 54 | 55 | override fun query(uri: Uri, projection: Array, selection: String, 56 | selectionArgs: Array, sortOrder: String): Cursor { 57 | throw UnsupportedOperationException() 58 | } 59 | 60 | override fun getType(uri: Uri): String { 61 | throw UnsupportedOperationException() 62 | } 63 | 64 | override fun insert(uri: Uri, values: ContentValues): Uri { 65 | throw UnsupportedOperationException() 66 | } 67 | 68 | override fun delete(uri: Uri, selection: String, selectionArgs: Array): Int { 69 | throw UnsupportedOperationException() 70 | } 71 | 72 | override fun update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array): Int { 73 | throw UnsupportedOperationException() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/org/protonaosp/elmyra/settings/ToggleTileService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Proton AOSP Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package org.protonaosp.elmyra.settings 18 | 19 | import android.content.Context 20 | import android.content.SharedPreferences 21 | import android.service.quicksettings.Tile 22 | import android.service.quicksettings.TileService 23 | import androidx.preference.PreferenceManager 24 | 25 | import org.protonaosp.elmyra.R 26 | import org.protonaosp.elmyra.getDePrefs 27 | import org.protonaosp.elmyra.getEnabled 28 | 29 | class ToggleTileService : TileService(), SharedPreferences.OnSharedPreferenceChangeListener { 30 | private lateinit var prefs: SharedPreferences 31 | 32 | override fun onStartListening() { 33 | prefs = getDePrefs() 34 | prefs.registerOnSharedPreferenceChangeListener(this) 35 | update() 36 | } 37 | 38 | override fun onStopListening() { 39 | prefs.unregisterOnSharedPreferenceChangeListener(this) 40 | } 41 | 42 | private fun update(state: Boolean? = null) { 43 | qsTile.state = if (state ?: prefs.getEnabled(this)) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE 44 | qsTile.updateTile() 45 | } 46 | 47 | override fun onClick() { 48 | val newState = !prefs.getEnabled(this) 49 | prefs.edit().putBoolean(getString(R.string.pref_key_enabled), newState).commit() 50 | update(newState) 51 | } 52 | 53 | override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { 54 | if (key == getString(R.string.pref_key_enabled)) { 55 | update() 56 | } 57 | } 58 | } 59 | --------------------------------------------------------------------------------