2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-31 14:15:14 +00:00

Compare commits

..

20 Commits

Author SHA1 Message Date
Albert Vaca Cintora
d114ff4c1c Use getDefault to get the SSLContext 2023-04-20 18:55:44 +02:00
Dmitry Yudin
9e5c9ca11a Migrate MainActivity to Kotlin
And replace deprecated onBackPressed with OnBackPressedCallback(s)
2023-04-20 13:54:27 +02:00
l10n daemon script
f9e74caa50 GIT_SILENT made messages (after extraction) 2023-04-19 00:49:25 +00:00
l10n daemon script
6feb8f478c GIT_SILENT Sync po/docbooks with svn 2023-04-18 01:57:48 +00:00
l10n daemon script
2e38789836 GIT_SILENT made messages (after extraction) 2023-04-18 00:47:09 +00:00
l10n daemon script
1f106ee9f1 GIT_SILENT Sync po/docbooks with svn 2023-04-17 02:36:29 +00:00
l10n daemon script
86e3faf75a GIT_SILENT made messages (after extraction) 2023-04-17 00:48:00 +00:00
Albert Vaca Cintora
9379d89d03 Lower delay between broadcasts 2023-04-16 11:46:53 +02:00
Albert Vaca Cintora
90787911fa Fix subtitle not disappearing when changing fragment 2023-04-16 11:42:12 +02:00
l10n daemon script
8b8106bad8 GIT_SILENT made messages (after extraction) 2023-04-16 00:47:50 +00:00
l10n daemon script
b20ccf16bd GIT_SILENT made messages (after extraction) 2023-04-15 00:48:50 +00:00
l10n daemon script
b4f8f1befa GIT_SILENT made messages (after extraction) 2023-04-14 00:46:17 +00:00
Albert Vaca Cintora
61190189ec Release 1.24.1 2023-04-13 19:10:54 +02:00
Albert Vaca Cintora
4c6cda711f Fix "find my phone" notification being dismissable
Dismissing the notification caused the alarm to keep playing without a
way to stop it.

BUG: 446349
2023-04-13 19:10:12 +02:00
Albert Vaca Cintora
64b32003cc Fix cards still visibile if device got unpaired with the app open 2023-04-13 18:55:49 +02:00
Albert Vaca Cintora
39fb60b81b Rename binding to pairingBinding 2023-04-13 18:55:28 +02:00
Albert Vaca Cintora
431312fcd0 Bump dependencies 2023-04-13 18:37:27 +02:00
Albert Vaca Cintora
6c8c6dd63e Fix compatibility with API 20 2023-04-13 17:56:13 +02:00
Dmitry Yudin
8aeefded7c Main activity responsive layout 2023-04-13 17:56:13 +02:00
Albert Vaca Cintora
e2dbc39e3a Do not force TLS v1
Stop specifying the TLS version we want and let the system chose

Co-authored-by: Daniel Tang <danielzgtg.opensource@gmail.com>
2023-04-13 11:07:29 +00:00
28 changed files with 463 additions and 475 deletions

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

BIN
.idea/icon.png generated

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.kde.kdeconnect_tp"
android:versionCode="12400"
android:versionName="1.24.0">
android:versionCode="12401"
android:versionName="1.24.1">
<supports-screens
android:anyDensity="true"
@@ -59,7 +59,8 @@
android:networkSecurityConfig="@xml/network_security_config"
android:localeConfig="@xml/locales_config"
android:theme="@style/KdeConnectTheme.NoActionBar"
android:name="org.kde.kdeconnect.MyApplication">
android:name="org.kde.kdeconnect.MyApplication"
android:enableOnBackInvokedCallback="true">
<receiver
android:name="com.android.mms.transaction.PushReceiver"

View File

@@ -4,7 +4,7 @@ import com.android.build.gradle.api.ApplicationVariant
import com.github.jk1.license.render.TextReportRenderer
buildscript {
ext.kotlin_version = '1.8.0'
ext.kotlin_version = '1.8.10'
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@@ -136,19 +136,19 @@ ext {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.media:media:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2019-06-30 11:38+0200\n"
"PO-Revision-Date: 2023-04-10 14:10\n"
"PO-Revision-Date: 2023-04-16 12:31\n"
"Last-Translator: Albert Vaca Cintora <albertvaka@gmail.com>\n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">Отдалечената връзка с клавиатурата е активна</string>
<string name="remotekeyboard_multiple_connections">Има повече от една отдалечена връзка за клавиатура, изберете устройството за конфигуриране</string>
<string name="open_mousepad">Отдалечен вход</string>
<string name="mousepad_info">Преместете пръст на екрана, за да преместите курсора на мишката. Докоснете за щракване и използвайте два/три пръста за десни и средни бутони. Използвайте 2 пръста за превъртане.Използвайте дълго натискане за влачене. Функцията на жироскопската мишка може да бъде активирана в настройките на плъгина</string>
<string name="mousepad_keyboard_input_not_supported">Въвеждането от клавиатурата не се поддържа от сдвоеното устройство</string>
<string name="mousepad_single_tap_settings_title">Задаване на действие с натискане с един пръст</string>
<string name="mousepad_double_tap_settings_title">Задаване на действие за докосване с два пръста</string>
<string name="mousepad_triple_tap_settings_title">Задаване на действие с докосване с три пръста</string>
<string name="mousepad_sensitivity_settings_title">Настройка на чувствителността на тъчпада</string>
<string name="mousepad_mouse_buttons_title">Показване на бутони на мишката</string>
<string name="mousepad_acceleration_profile_settings_title">Задаване на ускорение на показалеца</string>
<string name="mousepad_scroll_direction_title">Обръщане на посоката на превъртане</string>
<string-array name="mousepad_tap_entries">

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">Vzdálené připojení klávesnice je aktivní</string>
<string name="remotekeyboard_multiple_connections">Je k dispozici více než jedno připojení klávesnice. Vyberte zařízení pro jeho nastavení.</string>
<string name="open_mousepad">Vzdálený vstup</string>
<string name="mousepad_info">Pohybujte prstem po obrazovce pro pohybování kurzorem myši. Ťukněte pro kliknutí a použijte dva/tři prsty jako pravé a prostřední tlačítko. Použijte 2 prsty pro posunování. Pro přetažení dlouze podržte. Funkčnost gyro myš lze povolit v předvolbách modulu.</string>
<string name="mousepad_keyboard_input_not_supported">Vstup pomocí klávesnice není spárovaným zařízením podporován</string>
<string name="mousepad_single_tap_settings_title">Nastavit činnost pro ťuknutí prstem</string>
<string name="mousepad_double_tap_settings_title">Nastavit činnost pro ťuknutí dvěma prsty</string>
<string name="mousepad_triple_tap_settings_title">Nastavit činnost pro ťuknutí třemi prsty</string>
<string name="mousepad_sensitivity_settings_title">Nastavit citlivost touchpadu</string>
<string name="mousepad_mouse_buttons_title">Zobrazit tlačítka myši</string>
<string name="mousepad_acceleration_profile_settings_title">Nastavit akceleraci ukazatele</string>
<string name="mousepad_scroll_direction_title">Obrácený směr posunu</string>
<string-array name="mousepad_tap_entries">

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">La conexión remota de teclado está activa</string>
<string name="remotekeyboard_multiple_connections">Hay más de una conexión remota de teclado, seleccione el dispositivo a configurar</string>
<string name="open_mousepad">Entrada remota</string>
<string name="mousepad_info">Mueva un dedo sobre la pantalla para mover el cursor del ratón. Pulse para ejecutar un clic y use dos/tres dedos para emular los botones derecho y central. Use 2 dedos para desplazar las pantalla. Use una pulsación larga para arrastrar y soltar. La funcionalidad de la rueda del ratón puede ser activada desde las preferencias del complemento.</string>
<string name="mousepad_keyboard_input_not_supported">Entrada de teclado no soportada por el dispositivo vinculado.</string>
<string name="mousepad_single_tap_settings_title">Establecer la acción al pulsar con un dedo</string>
<string name="mousepad_double_tap_settings_title">Establecer la acción al pulsar con dos dedos</string>
<string name="mousepad_triple_tap_settings_title">Establecer la acción al pulsar con tres dedos</string>
<string name="mousepad_sensitivity_settings_title">Establecer sensibilidad del panel táctil</string>
<string name="mousepad_mouse_buttons_title">Mostrar botones del ratón</string>
<string name="mousepad_acceleration_profile_settings_title">Establecer la aceleración del puntero</string>
<string name="mousepad_scroll_direction_title">Invertir dirección de desplazamiento</string>
<string-array name="mousepad_tap_entries">

View File

@@ -180,7 +180,7 @@
</string-array>
<string name="mpris_notification_settings_title">Erakutsi euskarri kontrolaren jakinarazpena</string>
<string name="mpris_notification_settings_summary">Utzi zure euskarri-jotzaileak kontrolatzen KDE Connect ireki gabe</string>
<string name="share_to">Partekatu honi...</string>
<string name="share_to">Partekatu honekin...</string>
<string name="protocol_version_newer">Gailu honek protokoloaren bertsio berriago bat erabiltzen du</string>
<string name="plugin_settings_with_name">%s ezarpenak</string>
<string name="invalid_device_name">Gailuaren izen baliogabea</string>

View File

@@ -243,6 +243,7 @@
<string name="close">Sulje</string>
<string name="plugins_need_permission">Jotkin liitännäiset vaativat toimiakseen lisäkäyttöoikeuksia (lisätietoa napsauttamalla):</string>
<string name="permission_explanation">Liitännäinen tarvitsee toimiakseen lisäkäyttöoikeuksia</string>
<string name="all_permissions_granted">Kaikki oikeudet myönnetty 🎉</string>
<string name="optional_permission_explanation">Kaikkien toimintojen käyttämiseksi sinun on annettava lisäkäyttöoikeuksia</string>
<string name="plugins_need_optional_permission">Jotkin liitännäisten ominaisuudet eivät ole käytössä puuttuvien käyttöoikeuksien takia (lisätietoa napsauttamalla):</string>
<string name="share_optional_permission_explanation">Talletustilan käyttö on sallittava tiedostojen vastaanottamiseksi</string>

View File

@@ -51,6 +51,7 @@
<string name="remotekeyboard_connected">La connexion au clavier sans fil est active</string>
<string name="remotekeyboard_multiple_connections">Plusieurs connexions à des claviers sans fil sont disponibles, sélectionnez le périphérique à configurer</string>
<string name="open_mousepad">Contrôle distant</string>
<string name="mousepad_info">Faites glisser votre doigt sur l\'écran pour déplacer le pointeur de la souris. Tapotez pour cliquer et utilisez deux / trois doigts pour les clics droit et centre. Utilisez 2 doigts pour faire un défilement. Faites un appui prolongé pour réaliser un glisser-déposer. La fonctionnalité de gyroscope de souris peut être activée à partir des préférences de module externe.</string>
<string name="mousepad_keyboard_input_not_supported">La saisie par le clavier n\'est pas pris en charge par le périphérique appairée.</string>
<string name="mousepad_single_tap_settings_title">Définir une action pour tapotage avec un doigt</string>
<string name="mousepad_double_tap_settings_title">Action pour l\'appui à deux doigts</string>

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">A conexión de teclado remoto está activa.</string>
<string name="remotekeyboard_multiple_connections">Hai máis dunha conexión de teclado remoto, seleccione o dispositivo para configurar.</string>
<string name="open_mousepad">Entrada remota</string>
<string name="mousepad_info">Mova un dedo na pantalla para mover o cursor do rato. Toque para facer clic, e use dous ou tres dedos para os botóns secundario e central. Use dous dedos para desprazar. Prema durante un tempo para arrastrar e soltar. A funcionalidade de rato de xiroscopio pode activarse desde a configuración do complemento.</string>
<string name="mousepad_keyboard_input_not_supported">O dispositivo emparellado non permite entrada de teclado</string>
<string name="mousepad_single_tap_settings_title">Definir a acción de tocar cun dedo</string>
<string name="mousepad_double_tap_settings_title">Definir a acción de tocar con dous dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a acción de tocar con tres dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do punteiro táctil</string>
<string name="mousepad_mouse_buttons_title">Mostrar os botóns do rato</string>
<string name="mousepad_acceleration_profile_settings_title">Definir a aceleración do punteiro</string>
<string name="mousepad_scroll_direction_title">Inverter a dirección de desprazamento</string>
<string-array name="mousepad_tap_entries">

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">La connessione della tastiera remota è attiva</string>
<string name="remotekeyboard_multiple_connections">Ci sono più connessioni di tastiere remote, seleziona il dispositivo da configurare</string>
<string name="open_mousepad">Impulso remoto</string>
<string name="mousepad_info">Muovi un dito sullo schermo per spostare il puntatore del mouse. Tocca per un clic e usa due/tre dita per i pulsanti destro e centrale. Utilizza 2 dita per scorrere. Utilizza una pressione lunga per trascinare e rilasciare. È possibile abilitare la funzionalità del mouse giroscopico dalle preferenze dell\'estensione</string>
<string name="mousepad_keyboard_input_not_supported">Immissione da tastiera non supportata dal dispositivo associato</string>
<string name="mousepad_single_tap_settings_title">Imposta azione per il tocco a un dito</string>
<string name="mousepad_double_tap_settings_title">Imposta azione per il tocco a due dita</string>
<string name="mousepad_triple_tap_settings_title">Imposta azione per il tocco a tre dita</string>
<string name="mousepad_sensitivity_settings_title">Imposta la sensibilità del touchpad</string>
<string name="mousepad_mouse_buttons_title">Mostra i pulsanti del mouse</string>
<string name="mousepad_acceleration_profile_settings_title">Imposta accelerazione del puntatore</string>
<string name="mousepad_scroll_direction_title">Inverti direzione di scorrimento</string>
<string-array name="mousepad_tap_entries">

View File

@@ -51,7 +51,6 @@
<string name="remotekeyboard_connected">Eksternt tastatursamband er verksamt</string>
<string name="remotekeyboard_multiple_connections">Det finst meir enn eitt eksternt tastatursamband (vel eining å setja opp)</string>
<string name="open_mousepad">Fjernstyring</string>
<string name="mousepad_info">Dra ein finger over skjermen for å flytta peikaren på datamaskina. Trykk for å klikka, og bruk to eller tre fingrar for høvesvis høgre- og midtknappen. Bruk to fingrar for å rulla. Trykk lenge for å dra og sleppa.</string>
<string name="mousepad_keyboard_input_not_supported">Tekst frå tastaturet er ikkje støtta av den para eininga</string>
<string name="mousepad_single_tap_settings_title">Vel handling for éinfingertrykk</string>
<string name="mousepad_double_tap_settings_title">Vel handling for tofingertrykk</string>
@@ -99,7 +98,6 @@
<string name="battery_status_format">Batteri: %d %%</string>
<string name="battery_status_low_format">Batteri: %d %%  lågt batterinivå</string>
<string name="battery_status_charging_format">Batteri: %d %%  ladar</string>
<string name="battery_status_unknown">Batteriinformasjon er ikkje tilgjengeleg</string>
<string name="category_connected_devices">Tilkopla einingar</string>
<string name="category_not_paired_devices">Tilgjengelege einingar</string>
<string name="category_remembered_devices">Hugsa einingar</string>
@@ -375,7 +373,6 @@
<string name="clear_compose">Tøm</string>
<string name="send_compose">Send</string>
<string name="open_compose_send">Skriv tekst</string>
<string name="app_description">Fleirplattforms-app for kommunikasjon på tvers av einingar (for eksempel mellom telefon og datamaskin)</string>
<string name="about_kde_about">&lt;h1&gt;Om&lt;/h1&gt; &lt;p&gt;KDE er eit verdsfemnande fellesskap av eldsjeler som programmerer, teiknar, komponerer, dokumenterer, set om eller hjelper til på andre måtar med utvikling av &lt;a href=https://www.gnu.org/philosophy/free-sw.html&gt;fri programvare&lt;/a&gt;. Me har laga brukarflata Plasma, hundrevis av program og dei mange programbiblioteka desse byggjer på.&lt;/p&gt; &lt;p&gt;KDE er eit fellesskap der inga einskild gruppe, firma eller organisasjon har eigarskap til produkta eller styrer retninga den vidare utviklinga skal gå i. Derimot arbeider me saman om å oppnå vårt felles mål om å laga fri programvare i verdsklasse. Alle er &lt;a href=https://community.kde.org/Get_Involved&gt;velkomne til å bidra&lt;/a&gt;  du òg.&lt;/p&gt;Du finn meir informasjon om KDE og programma me utviklar på &lt;a href=https://www.kde.org/&gt;https://www.kde.org/&lt;/a&gt;.</string>
<string name="about_kde_report_bugs_or_wishes">&lt;h1&gt;Meld frå om feil eller ønskje&lt;/h1&gt; &lt;p&gt;Ein kan alltid forbetra programvare, og KDE-gruppa arbeider heile tida for det. Men du, som brukar, må melda frå til oss når noko ikkje verkar slik du forventar, eller når noko kunne vore gjort betre.&lt;/p&gt; &lt;p&gt;KDE har eit feilsporingssystem. Gå til &lt;a href=https://bugs.kde.org/&gt;https://bugs.kde.org/&lt;/a&gt; eller vel «Meld frå om feil» på «Om»-sida for å melda frå om feil.&lt;/p&gt; Om du har framlegg til forbetringar, kan du gjerne registrera òg desse i feilsporingssystemet. Sjå då til at du har markert feilrapporten med «Wishlist».</string>
<string name="about_kde_join_kde">&lt;h1&gt;Vert med i KDE&lt;/h1&gt; &lt;p&gt;Du treng ikkje vera programutviklar for å hjelpa til med KDE. Du kan arbeida med omsetjingar, laga grafikk, tema, lydar eller betre hjelpetekstar. Her er noko for alle!&lt;/p&gt; &lt;p&gt;&lt;a href=https://community.kde.org/Get_Involved&gt;https://community.kde.org/Get_Involved&lt;/a&gt; finn du informasjon om nokre prosjekt du kan delta i.&lt;/p&gt; Om du vil ha meir informasjon eller dokumentasjon, finn du det du treng på &lt;a href=https://techbase.kde.org/&gt;https://techbase.kde.org/&lt;/a&gt;.</string>

View File

@@ -51,6 +51,7 @@
<string name="remotekeyboard_connected">Połączenie zdalnej klawiatury jest nawiązane</string>
<string name="remotekeyboard_multiple_connections">Nawiązano więcej niż jedno połączenie zdalnej klawiatury, wybierz urządzenie do ustawienia</string>
<string name="open_mousepad">Zdalne sterowanie</string>
<string name="mousepad_info">Przesuwanie palcem po ekranie przesuwa wskaźnik myszy. Stuknięcie jednym, dwoma i trzema palcami wywołuje odpowiednio naciśnięcie lewym, prawym i środkowym przyciskiem myszy. Dwa palce przewijają. Długie naciśnięcie rozpoczyna czynność przeciągania i upuszczania. Zachowania żyroskopowe myszy mogą być włączone z poziomu ustawień wtyczki.</string>
<string name="mousepad_keyboard_input_not_supported">Wpisywanie z klawiatury jest nieobsługiwane przez sparowane urządzenie</string>
<string name="mousepad_single_tap_settings_title">Ustaw działanie po stuknięciu jednym palcem</string>
<string name="mousepad_double_tap_settings_title">Ustaw działanie po stuknięciu dwoma palcami</string>

View File

@@ -51,11 +51,13 @@
<string name="remotekeyboard_connected">A ligação ao teclado remoto está activa</string>
<string name="remotekeyboard_multiple_connections">Existe mais que uma ligação a teclados remotos; seleccione o dispositivo a configurar</string>
<string name="open_mousepad">Introdução remota de dados</string>
<string name="mousepad_info">Mova um dedo pelo ecrã para mover o cursor do rato. Dê um toque para carregar no botão esquerdo e use dois/três dedos para os botões direito e do meio. Use 2 dedos para deslocar-se. Use uma pressão longa para arrastar e largar. A funcionalidade giroscópica do rato pode ser activada a partir das preferências do \'plugin\'</string>
<string name="mousepad_keyboard_input_not_supported">O uso do teclado não é suportado pelo dispositivo emparelhado</string>
<string name="mousepad_single_tap_settings_title">Definir a acção do toque com um dedo</string>
<string name="mousepad_double_tap_settings_title">Definir a acção do toque com dois dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a acção do toque com três dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do rato por toque</string>
<string name="mousepad_mouse_buttons_title">Mostrar os botões do rato</string>
<string name="mousepad_acceleration_profile_settings_title">Definir a aceleração do cursor</string>
<string name="mousepad_scroll_direction_title">Direcção de Deslocamento Inversa</string>
<string-array name="mousepad_tap_entries">

View File

@@ -51,6 +51,7 @@
<string name="remotekeyboard_connected">தொலை விசைப்பலகை இணைப்பு செயலில் உள்ளது</string>
<string name="remotekeyboard_multiple_connections">பல தொலை விசைப்பலகை இணைப்புகள் உள்ளன. அமைக்க வேண்டிய சாதனத்தை தேர்ந்தெடுங்கள்</string>
<string name="open_mousepad">தொலை உள்ளீடு</string>
<string name="mousepad_info">சுட்டிக்குறியை நகர்த்த ஒரு விரலை திரையில் நகர்த்தவும். \'க்ளிக்\' செய்வதற்கு தட்டுங்கள். வலது/நடு சுட்டி பட்டன்களுக்கு இரண்டு/மூன்று விரல்களை பயன்படுத்தவும். இரண்டு விரல்களைக் கொண்டு உருளவும். இழுத்து போடுவதற்கு நீண்ட அழுத்தத்தை பயன்படுத்தவும். சுழல்காட்டி சுட்டியைபோல் செயல்பட வேண்டுமெனில் செருகுநிரல் அமைப்புகளில் உரிய அம்சத்தை இயக்கலாம்</string>
<string name="mousepad_keyboard_input_not_supported">இணைக்கப்பட்டுள்ள சாதனம், விசைப்பலகை உள்ளீட்டை ஆதரிக்காது</string>
<string name="mousepad_single_tap_settings_title">ஒருவிரலால் தட்டுவதற்குரிய செயலை அமை</string>
<string name="mousepad_double_tap_settings_title">இரண்டு விரல்களால் தட்டுவதற்குரிய செயலை அமை</string>

View File

@@ -51,6 +51,7 @@
<string name="remotekeyboard_connected">远程键盘连接已启用</string>
<string name="remotekeyboard_multiple_connections">发现多个远程键盘连接,请选择要配置的设备</string>
<string name="open_mousepad">远程输入</string>
<string name="mousepad_info">在屏幕上移动手指来移动光标。轻击代表左键,双指或三指点击代表右键或中键。用双指滚动。用长按来拖放。基于陀螺仪的空中鼠标功能可以在插件的首选项中启用。</string>
<string name="mousepad_keyboard_input_not_supported">配对的设备不支持键盘输入</string>
<string name="mousepad_single_tap_settings_title">设置单指点击操作</string>
<string name="mousepad_double_tap_settings_title">设置双指点击操作</string>

View File

@@ -66,7 +66,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
private DatagramSocket udpServer;
private long lastBroadcast = 0;
private final static long delayBetweenBroadcasts = 500;
private final static long delayBetweenBroadcasts = 200;
private boolean listening = false;
@@ -385,7 +385,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}
if (bytes != null) {
//Log.e("KDE/LanLinkProvider","Sending packet to "+iplist.size()+" ips");
Log.i("KDE/LanLinkProvider","Sending broadcast to "+iplist.size()+" ips");
for (String ipstr : iplist) {
try {
InetAddress client = InetAddress.getByName(ipstr);

View File

@@ -209,7 +209,7 @@ public class SslHelper {
trustManagerFactory.init(keyStore);
// Setup custom trust manager if device not trusted
SSLContext tlsContext = SSLContext.getInstance("TLSv1"); //Newer TLS versions are only supported on API 16+
SSLContext tlsContext = SSLContext.getDefault();
if (isDeviceTrusted) {
tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
} else {

View File

@@ -151,6 +151,7 @@ public class FindMyPhonePlugin extends Plugin {
.setFullScreenIntent(pendingIntent, true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setOngoing(true)
.setContentTitle(context.getString(R.string.findmyphone_found));
notification.setGroup("BackgroundService");

View File

@@ -61,8 +61,8 @@ class DeviceFragment : Fragment() {
*
* Used to start and retry pairing.
*/
private var binding: ViewPairRequestBinding? = null
private fun requireBinding() = binding ?: throw IllegalStateException("binding is not set")
private var pairingBinding: ViewPairRequestBinding? = null
private fun requirePairingBinding() = pairingBinding ?: throw IllegalStateException("binding is not set")
/**
* Cannot-communicate ViewBinding.
@@ -94,7 +94,7 @@ class DeviceFragment : Fragment() {
val deviceBinding = deviceBinding ?: return null
// Inner binding for the layout shown when we're not paired yet...
binding = deviceBinding.pairRequest
pairingBinding = deviceBinding.pairRequest
// ...and for when pairing doesn't (or can't) work
errorBinding = deviceBinding.pairError
@@ -102,8 +102,8 @@ class DeviceFragment : Fragment() {
device = it.getDevice(deviceId)
}
requireBinding().pairButton.setOnClickListener {
with(requireBinding()) {
requirePairingBinding().pairButton.setOnClickListener {
with(requirePairingBinding()) {
pairButton.visibility = View.GONE
pairMessage.text = null
pairVerification.visibility = View.VISIBLE
@@ -112,13 +112,13 @@ class DeviceFragment : Fragment() {
}
device?.requestPairing()
}
requireBinding().acceptButton.setOnClickListener {
requirePairingBinding().acceptButton.setOnClickListener {
device?.apply {
acceptPairing()
requireBinding().pairingButtons.visibility = View.GONE
requirePairingBinding().pairingButtons.visibility = View.GONE
}
}
requireBinding().rejectButton.setOnClickListener {
requirePairingBinding().rejectButton.setOnClickListener {
device?.apply {
//Remove listener so buttons don't show for a while before changing the view
removePluginsChangedListener(pluginsChangedListener)
@@ -155,7 +155,7 @@ class DeviceFragment : Fragment() {
device.removePairingCallback(pairingCallback)
}
super.onDestroyView()
binding = null
pairingBinding = null
errorBinding = null
deviceBinding = null
}
@@ -243,28 +243,36 @@ class DeviceFragment : Fragment() {
mActivity?.runOnUiThread(object : Runnable {
override fun run() {
if (device.isPairRequestedByPeer) {
requireBinding().pairMessage.setText(R.string.pair_requested)
requireBinding().pairVerification.visibility = View.VISIBLE
requireBinding().pairVerification.text =
SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
requireBinding().pairingButtons.visibility = View.VISIBLE
requireBinding().pairProgress.visibility = View.GONE
requireBinding().pairButton.visibility = View.GONE
requireBinding().pairRequestButtons.visibility = View.VISIBLE
with (requirePairingBinding()) {
pairMessage.setText(R.string.pair_requested)
pairVerification.visibility = View.VISIBLE
pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
pairingButtons.visibility = View.VISIBLE
pairProgress.visibility = View.GONE
pairButton.visibility = View.GONE
pairRequestButtons.visibility = View.VISIBLE
}
with (requireDeviceBinding()) {
permissionsList.visibility = View.GONE
pluginsList.visibility = View.GONE
}
} else {
val paired = device.isPaired
val reachable = device.isReachable
requireBinding().pairingButtons.visibility = if (paired) View.GONE else View.VISIBLE
requirePairingBinding().pairingButtons.visibility = if (paired) View.GONE else View.VISIBLE
if (paired && !reachable) {
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
requireErrorBinding().notReachableMessage.visibility = View.VISIBLE
requireDeviceBinding().permissionsList.visibility = View.GONE
requireDeviceBinding().pluginsList.visibility = View.GONE
} else {
} else if (paired) {
requireErrorBinding().errorMessageContainer.visibility = View.GONE
requireErrorBinding().notReachableMessage.visibility = View.GONE
requireDeviceBinding().permissionsList.visibility = View.VISIBLE
requireDeviceBinding().pluginsList.visibility = View.VISIBLE
} else {
requireDeviceBinding().permissionsList.visibility = View.GONE
requireDeviceBinding().pluginsList.visibility = View.GONE
}
try {
if (paired && reachable) {
@@ -332,7 +340,7 @@ class DeviceFragment : Fragment() {
override fun pairingFailed(error: String) {
mActivity?.runOnUiThread {
with(requireBinding()) {
with(requirePairingBinding()) {
pairMessage.text = error
pairVerification.text = null
pairVerification.visibility = View.GONE
@@ -346,7 +354,7 @@ class DeviceFragment : Fragment() {
override fun unpaired() {
mActivity?.runOnUiThread {
with(requireBinding()) {
with(requirePairingBinding()) {
pairMessage.setText(R.string.device_not_paired)
pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE
@@ -408,4 +416,8 @@ class DeviceFragment : Fragment() {
}
}
override fun onDetach() {
super.onDetach()
mActivity?.supportActionBar?.subtitle = null
}
}

View File

@@ -1,429 +0,0 @@
package org.kde.kdeconnect.UserInterface;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment;
import org.kde.kdeconnect.UserInterface.About.AboutFragment;
import org.kde.kdeconnect.UserInterface.About.ApplicationAboutDataKt;
import org.kde.kdeconnect_tp.R;
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection
private static final int MENU_ENTRY_SETTINGS = 2;
private static final int MENU_ENTRY_ABOUT = 3;
private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu
private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet
private static final int STORAGE_LOCATION_CONFIGURED = 2020;
private static final String STATE_SELECTED_MENU_ENTRY = "selected_entry"; //Saved only in onSaveInstanceState
private static final String STATE_SELECTED_DEVICE = "selected_device"; //Saved persistently in preferences
public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER;
public static final String PAIR_REQUEST_STATUS = "pair_req_status";
public static final String PAIRING_ACCEPTED = "accepted";
public static final String PAIRING_REJECTED = "rejected";
public static final String PAIRING_PENDING = "pending";
public static final String EXTRA_DEVICE_ID = "deviceId";
public static final String FLAG_FORCE_OVERVIEW = "forceOverview";
private NavigationView mNavigationView;
private DrawerLayout mDrawerLayout;
private TextView mNavViewDeviceName;
private String mCurrentDevice;
private int mCurrentMenuEntry;
private SharedPreferences preferences;
private final HashMap<MenuItem, String> mMapMenuToDeviceId = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DeviceHelper.initializeDeviceId(this);
final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mNavigationView = binding.navigationDrawer;
mDrawerLayout = binding.drawerLayout;
View mDrawerHeader = mNavigationView.getHeaderView(0);
mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name);
ImageView mNavViewDeviceType = mDrawerHeader.findViewById(R.id.device_type);
setSupportActionBar(binding.toolbarLayout.toolbar);
ActionBar actionBar = getSupportActionBar();
if (mDrawerLayout != null) {
ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.string.open, /* "open drawer" description */
R.string.close /* "close drawer" description */
);
mDrawerLayout.addDrawerListener(mDrawerToggle);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
}
preferences = getSharedPreferences("stored_menu_selection", Context.MODE_PRIVATE);
// Note: The preference changed listener should be registered before getting the name, because getting
// it can trigger a background fetch from the internet that will eventually update the preference
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
String deviceName = DeviceHelper.getDeviceName(this);
mNavViewDeviceType.setImageDrawable(DeviceHelper.getDeviceType(this).getIcon(this));
mNavViewDeviceName.setText(deviceName);
mNavigationView.setNavigationItemSelectedListener(menuItem -> {
mCurrentMenuEntry = menuItem.getItemId();
switch (mCurrentMenuEntry) {
case MENU_ENTRY_ADD_DEVICE:
mCurrentDevice = null;
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
setContentFragment(new PairingFragment());
break;
case MENU_ENTRY_SETTINGS:
mCurrentDevice = null;
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
setContentFragment(new SettingsFragment());
break;
case MENU_ENTRY_ABOUT:
mCurrentDevice = null;
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this))));
break;
default:
String deviceId = mMapMenuToDeviceId.get(menuItem);
onDeviceSelected(deviceId);
break;
}
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mNavigationView);
}
return true;
});
// Decide which menu entry should be selected at start
String savedDevice;
int savedMenuEntry;
if (getIntent().hasExtra(FLAG_FORCE_OVERVIEW)) {
Log.i("MainActivity", "Requested to start main overview");
savedDevice = null;
savedMenuEntry = MENU_ENTRY_ADD_DEVICE;
} else if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
Log.i("MainActivity", "Loading selected device from parameter");
savedDevice = getIntent().getStringExtra(EXTRA_DEVICE_ID);
savedMenuEntry = MENU_ENTRY_DEVICE_UNKNOWN;
// If pairStatus is not empty, then the user has accepted/reject the pairing from the notification
String pairStatus = getIntent().getStringExtra(PAIR_REQUEST_STATUS);
if (pairStatus != null) {
Log.i("MainActivity", "pair status is " + pairStatus);
savedDevice = onPairResultFromNotification(savedDevice, pairStatus);
if (savedDevice == null) {
savedMenuEntry = MENU_ENTRY_ADD_DEVICE;
}
}
} else if (savedInstanceState != null) {
Log.i("MainActivity", "Loading selected device from saved activity state");
savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE);
savedMenuEntry = savedInstanceState.getInt(STATE_SELECTED_MENU_ENTRY, MENU_ENTRY_ADD_DEVICE);
} else {
Log.i("MainActivity", "Loading selected device from persistent storage");
savedDevice = preferences.getString(STATE_SELECTED_DEVICE, null);
savedMenuEntry = (savedDevice != null)? MENU_ENTRY_DEVICE_UNKNOWN : MENU_ENTRY_ADD_DEVICE;
}
mCurrentMenuEntry = savedMenuEntry;
mCurrentDevice = savedDevice;
mNavigationView.setCheckedItem(savedMenuEntry);
//FragmentManager will restore whatever fragment was there
if (savedInstanceState != null) {
Fragment frag = getSupportFragmentManager().findFragmentById(R.id.container);
if (!(frag instanceof DeviceFragment) || ((DeviceFragment)frag).getDeviceId().equals(savedDevice)) {
return;
}
}
// Activate the chosen fragment and select the entry in the menu
if (savedMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID && savedDevice != null) {
onDeviceSelected(savedDevice);
} else {
if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS) {
setContentFragment(new SettingsFragment());
} else if (mCurrentMenuEntry == MENU_ENTRY_ABOUT) {
setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this))));
} else {
setContentFragment(new PairingFragment());
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
}
private String onPairResultFromNotification(String deviceId, String pairStatus) {
assert(deviceId != null);
if (!pairStatus.equals(PAIRING_PENDING)) {
BackgroundService.RunCommand(this, service -> {
Device device = service.getDevice(deviceId);
if (device == null) {
Log.w("rejectPairing", "Device no longer exists: " + deviceId);
return;
}
if (pairStatus.equals(PAIRING_ACCEPTED)) {
device.acceptPairing();
} else if (pairStatus.equals(PAIRING_REJECTED)) {
device.rejectPairing();
}
});
}
if (pairStatus.equals(PAIRING_ACCEPTED) || pairStatus.equals(PAIRING_PENDING)) {
return deviceId;
} else {
return null;
}
}
private int deviceIdToMenuEntryId(String deviceId) {
for (HashMap.Entry<MenuItem, String> entry : mMapMenuToDeviceId.entrySet()) {
if (TextUtils.equals(entry.getValue(), deviceId)) { //null-safe
return entry.getKey().getItemId();
}
}
return MENU_ENTRY_DEVICE_UNKNOWN;
}
@Override
public void onBackPressed() {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mNavigationView)) {
mDrawerLayout.closeDrawer(mNavigationView);
} else if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS || mCurrentMenuEntry == MENU_ENTRY_ABOUT) {
mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE;
mNavigationView.setCheckedItem(MENU_ENTRY_ADD_DEVICE);
setContentFragment(new PairingFragment());
} else {
super.onBackPressed();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerLayout != null && item.getItemId() == android.R.id.home) {
mDrawerLayout.openDrawer(mNavigationView);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
private void updateDeviceList() {
BackgroundService.RunCommand(MainActivity.this, service -> {
Menu menu = mNavigationView.getMenu();
menu.clear();
mMapMenuToDeviceId.clear();
SubMenu devicesMenu = menu.addSubMenu(R.string.devices);
int id = MENU_ENTRY_DEVICE_FIRST_ID;
Collection<Device> devices = service.getDevices().values();
for (Device device : devices) {
if (device.isReachable() && device.isPaired()) {
MenuItem item = devicesMenu.add(Menu.FIRST, id++, 1, device.getName());
item.setIcon(device.getIcon());
item.setCheckable(true);
mMapMenuToDeviceId.put(item, device.getDeviceId());
}
}
MenuItem addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device);
addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp);
addDeviceItem.setCheckable(true);
MenuItem settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings);
settingsItem.setIcon(R.drawable.ic_settings_white_32dp);
settingsItem.setCheckable(true);
MenuItem aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about);
aboutItem.setIcon(R.drawable.ic_baseline_info_24);
aboutItem.setCheckable(true);
//Ids might have changed
if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) {
mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice);
}
mNavigationView.setCheckedItem(mCurrentMenuEntry);
});
}
@Override
protected void onStart() {
super.onStart();
BackgroundService.RunCommand(this, service -> {
service.onNetworkChange();
service.addDeviceListChangedCallback("MainActivity", unused -> updateDeviceList());
});
updateDeviceList();
}
@Override
protected void onStop() {
BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("MainActivity"));
super.onStop();
}
private static void uncheckAllMenuItems(Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
MenuItem item = menu.getItem(i);
if(item.hasSubMenu()) {
uncheckAllMenuItems(item.getSubMenu());
} else {
item.setChecked(false);
}
}
}
public void onDeviceSelected(String deviceId, boolean fromDeviceList) {
mCurrentDevice = deviceId;
preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply();
if (mCurrentDevice != null) {
mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId);
if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) {
uncheckAllMenuItems(mNavigationView.getMenu());
} else {
mNavigationView.setCheckedItem(mCurrentMenuEntry);
}
setContentFragment(DeviceFragment.Companion.newInstance(deviceId, fromDeviceList));
} else {
mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE;
mNavigationView.setCheckedItem(mCurrentMenuEntry);
setContentFragment(new PairingFragment());
}
}
private void setContentFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment)
.commit();
}
public void onDeviceSelected(String deviceId) {
onDeviceSelected(deviceId, false);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(STATE_SELECTED_DEVICE, mCurrentDevice);
outState.putInt(STATE_SELECTED_MENU_ENTRY, mCurrentMenuEntry);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RESULT_NEEDS_RELOAD) {
BackgroundService.RunCommand(this, service -> {
Device device = service.getDevice(mCurrentDevice);
device.reloadPluginsFromSettings();
});
} else if (requestCode == STORAGE_LOCATION_CONFIGURED && resultCode == RESULT_OK && data != null){
Uri uri = data.getData();
ShareSettingsFragment.saveStorageLocationPreference(this, uri);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode,permissions,grantResults);
boolean permissionsGranted = ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED);
if (permissionsGranted) {
int i = ArrayUtils.indexOf(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE);
boolean writeStoragePermissionGranted = (i != ArrayUtils.INDEX_NOT_FOUND &&
grantResults[i] == PackageManager.PERMISSION_GRANTED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && writeStoragePermissionGranted) {
// To get a writeable path manually on Android 10 and later for Share and Receive Plugin.
// Otherwise Receiving files will keep failing until the user chooses a path manually to receive files.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, STORAGE_LOCATION_CONFIGURED);
}
//New permission granted, reload plugins
BackgroundService.RunCommand(this, service -> {
Device device = service.getDevice(mCurrentDevice);
device.reloadPluginsFromSettings();
});
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (DeviceHelper.KEY_DEVICE_NAME_PREFERENCE.equals(key)) {
mNavViewDeviceName.setText(DeviceHelper.getDeviceName(this));
BackgroundService.RunCommand(this, BackgroundService::onNetworkChange); //Re-send our identity packet
}
}
}

View File

@@ -0,0 +1,397 @@
package org.kde.kdeconnect.UserInterface
import android.Manifest
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import com.google.android.material.navigation.NavigationView
import org.apache.commons.lang3.ArrayUtils
import org.kde.kdeconnect.BackgroundService
import org.kde.kdeconnect.Device
import org.kde.kdeconnect.Helpers.DeviceHelper
import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment
import org.kde.kdeconnect.UserInterface.About.AboutFragment.Companion.newInstance
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
import org.kde.kdeconnect.UserInterface.DeviceFragment.Companion.newInstance
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
private const val MENU_ENTRY_SETTINGS = 2
private const val MENU_ENTRY_ABOUT = 3
private const val MENU_ENTRY_DEVICE_FIRST_ID = 1000 //All subsequent ids are devices in the menu
private const val MENU_ENTRY_DEVICE_UNKNOWN = 9999 //It's still a device, but we don't know which one yet
private const val STORAGE_LOCATION_CONFIGURED = 2020
private const val STATE_SELECTED_MENU_ENTRY = "selected_entry" //Saved only in onSaveInstanceState
private const val STATE_SELECTED_DEVICE = "selected_device" //Saved persistently in preferences
class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val mNavigationView: NavigationView by lazy { binding.navigationDrawer }
private val mDrawerLayout: DrawerLayout? by lazy { binding.drawerLayout }
private lateinit var mNavViewDeviceName: TextView
private var mCurrentDevice: String? = null
private var mCurrentMenuEntry = 0
private set(value) {
field = value
//Enabling "go to default fragment on back" callback when user in settings or "about" fragment
mainFragmentCallback.isEnabled = value == MENU_ENTRY_SETTINGS || value == MENU_ENTRY_ABOUT
}
private val preferences: SharedPreferences by lazy { getSharedPreferences("stored_menu_selection", MODE_PRIVATE) }
private val mMapMenuToDeviceId = HashMap<MenuItem, String>()
private val closeDrawerCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
mDrawerLayout?.closeDrawer(mNavigationView)
}
}
private val mainFragmentCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
mCurrentMenuEntry = mCurrentDevice?.let { deviceIdToMenuEntryId(it) } ?: MENU_ENTRY_ADD_DEVICE
mNavigationView.setCheckedItem(mCurrentMenuEntry)
setContentFragment(mCurrentDevice?.let { newInstance(it, false) } ?: PairingFragment())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DeviceHelper.initializeDeviceId(this)
setContentView(binding.root)
val mDrawerHeader = mNavigationView.getHeaderView(0)
mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name)
val mNavViewDeviceType = mDrawerHeader.findViewById<ImageView>(R.id.device_type)
setSupportActionBar(binding.toolbarLayout.toolbar)
mDrawerLayout?.let {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val mDrawerToggle = DrawerToggle(it).apply { syncState() }
it.addDrawerListener(mDrawerToggle)
it.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START)
} ?: {
closeDrawerCallback.isEnabled = false
supportActionBar?.setDisplayShowHomeEnabled(false)
supportActionBar?.setHomeButtonEnabled(false)
}
// Note: The preference changed listener should be registered before getting the name, because getting
// it can trigger a background fetch from the internet that will eventually update the preference
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
val deviceName = DeviceHelper.getDeviceName(this)
mNavViewDeviceType?.setImageDrawable(DeviceHelper.getDeviceType(this).getIcon(this))
mNavViewDeviceName.text = deviceName
mNavigationView.setNavigationItemSelectedListener { menuItem: MenuItem ->
mCurrentMenuEntry = menuItem.itemId
when (mCurrentMenuEntry) {
MENU_ENTRY_ADD_DEVICE -> {
mCurrentDevice = null
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
setContentFragment(PairingFragment())
}
MENU_ENTRY_SETTINGS -> {
// mCurrentDevice = null
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
setContentFragment(SettingsFragment())
}
MENU_ENTRY_ABOUT -> {
// mCurrentDevice = null
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
setContentFragment(newInstance(getApplicationAboutData(this)))
}
else -> {
val deviceId = mMapMenuToDeviceId[menuItem]
onDeviceSelected(deviceId)
}
}
mDrawerLayout?.closeDrawer(mNavigationView)
true
}
// Decide which menu entry should be selected at start
var savedDevice: String?
var savedMenuEntry: Int
when {
intent.hasExtra(FLAG_FORCE_OVERVIEW) -> {
Log.i(this::class.simpleName, "Requested to start main overview")
savedDevice = null
savedMenuEntry = MENU_ENTRY_ADD_DEVICE
}
intent.hasExtra(EXTRA_DEVICE_ID) -> {
Log.i(this::class.simpleName, "Loading selected device from parameter")
savedDevice = intent.getStringExtra(EXTRA_DEVICE_ID)
savedMenuEntry = MENU_ENTRY_DEVICE_UNKNOWN
// If pairStatus is not empty, then the user has accepted/reject the pairing from the notification
val pairStatus = intent.getStringExtra(PAIR_REQUEST_STATUS)
if (pairStatus != null) {
Log.i(this::class.simpleName, "Pair status is $pairStatus")
savedDevice = onPairResultFromNotification(savedDevice, pairStatus)
if (savedDevice == null) {
savedMenuEntry = MENU_ENTRY_ADD_DEVICE
}
}
}
savedInstanceState != null -> {
Log.i(this::class.simpleName, "Loading selected device from saved activity state")
savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE)
savedMenuEntry = savedInstanceState.getInt(STATE_SELECTED_MENU_ENTRY, MENU_ENTRY_ADD_DEVICE)
}
else -> {
Log.i(this::class.simpleName, "Loading selected device from persistent storage")
savedDevice = preferences.getString(STATE_SELECTED_DEVICE, null)
savedMenuEntry = if (savedDevice != null) MENU_ENTRY_DEVICE_UNKNOWN else MENU_ENTRY_ADD_DEVICE
}
}
mCurrentMenuEntry = savedMenuEntry
mCurrentDevice = savedDevice
mNavigationView.setCheckedItem(savedMenuEntry)
//FragmentManager will restore whatever fragment was there
if (savedInstanceState != null) {
val frag = supportFragmentManager.findFragmentById(R.id.container)
if (frag !is DeviceFragment || frag.deviceId == savedDevice) return
}
// Activate the chosen fragment and select the entry in the menu
if (savedMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID && savedDevice != null) {
onDeviceSelected(savedDevice)
} else {
when (mCurrentMenuEntry) {
MENU_ENTRY_SETTINGS -> setContentFragment(SettingsFragment())
MENU_ENTRY_ABOUT -> setContentFragment(newInstance(getApplicationAboutData(this)))
else -> setContentFragment(PairingFragment())
}
}
onBackPressedDispatcher.addCallback(mainFragmentCallback)
onBackPressedDispatcher.addCallback(closeDrawerCallback)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this)
}
private fun onPairResultFromNotification(deviceId: String?, pairStatus: String): String? {
assert(deviceId != null)
if (pairStatus != PAIRING_PENDING) {
BackgroundService.RunCommand(this) { service: BackgroundService ->
val device = service.getDevice(deviceId)
if (device == null) {
Log.w(this::class.simpleName, "Reject pairing - device no longer exists: $deviceId")
return@RunCommand
}
when (pairStatus) {
PAIRING_ACCEPTED -> device.acceptPairing()
PAIRING_REJECTED -> device.rejectPairing()
}
}
}
return if (pairStatus == PAIRING_ACCEPTED || pairStatus == PAIRING_PENDING) deviceId else null
}
private fun deviceIdToMenuEntryId(deviceId: String?): Int {
for ((key, value) in mMapMenuToDeviceId) {
if (value == deviceId) {
return key.itemId
}
}
return MENU_ENTRY_DEVICE_UNKNOWN
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
mDrawerLayout?.openDrawer(mNavigationView)
true
} else {
super.onOptionsItemSelected(item)
}
}
private fun updateDeviceList() {
BackgroundService.RunCommand(this@MainActivity) { service: BackgroundService ->
val menu = mNavigationView.menu
menu.clear()
mMapMenuToDeviceId.clear()
val devicesMenu = menu.addSubMenu(R.string.devices)
var id = MENU_ENTRY_DEVICE_FIRST_ID
val devices: Collection<Device> = service.devices.values
for (device in devices) {
if (device.isReachable && device.isPaired) {
val item = devicesMenu.add(Menu.FIRST, id++, 1, device.name)
item.setIcon(device.icon)
item.setCheckable(true)
mMapMenuToDeviceId[item] = device.deviceId
}
}
val addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device)
addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp)
addDeviceItem.setCheckable(true)
val settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings)
settingsItem.setIcon(R.drawable.ic_settings_white_32dp)
settingsItem.setCheckable(true)
val aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about)
aboutItem.setIcon(R.drawable.ic_baseline_info_24)
aboutItem.setCheckable(true)
//Ids might have changed
if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) {
mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice)
}
mNavigationView.setCheckedItem(mCurrentMenuEntry)
}
}
override fun onStart() {
super.onStart()
BackgroundService.RunCommand(this) { service: BackgroundService ->
service.onNetworkChange()
service.addDeviceListChangedCallback(this::class.simpleName) { updateDeviceList() }
}
updateDeviceList()
}
override fun onStop() {
BackgroundService.RunCommand(this) { service: BackgroundService -> service.removeDeviceListChangedCallback(this::class.simpleName) }
super.onStop()
}
@JvmOverloads
fun onDeviceSelected(deviceId: String?, fromDeviceList: Boolean = false) {
mCurrentDevice = deviceId
preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply()
if (mCurrentDevice != null) {
mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId)
if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) {
uncheckAllMenuItems(mNavigationView.menu)
} else {
mNavigationView.setCheckedItem(mCurrentMenuEntry)
}
setContentFragment(newInstance(deviceId, fromDeviceList))
} else {
mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE
mNavigationView.setCheckedItem(mCurrentMenuEntry)
setContentFragment(PairingFragment())
}
}
private fun setContentFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, fragment)
.commit()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(STATE_SELECTED_DEVICE, mCurrentDevice)
outState.putInt(STATE_SELECTED_MENU_ENTRY, mCurrentMenuEntry)
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when {
requestCode == RESULT_NEEDS_RELOAD -> BackgroundService.RunCommand(this) { service: BackgroundService ->
val device = service.getDevice(mCurrentDevice)
device.reloadPluginsFromSettings()
}
requestCode == STORAGE_LOCATION_CONFIGURED && resultCode == RESULT_OK && data != null -> {
val uri = data.data
ShareSettingsFragment.saveStorageLocationPreference(this, uri)
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionsGranted = ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)
if (permissionsGranted) {
val i = ArrayUtils.indexOf(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)
val writeStoragePermissionGranted = i != ArrayUtils.INDEX_NOT_FOUND &&
grantResults[i] == PackageManager.PERMISSION_GRANTED
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && writeStoragePermissionGranted) {
// To get a writeable path manually on Android 10 and later for Share and Receive Plugin.
// Otherwise, Receiving files will keep failing until the user chooses a path manually to receive files.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, STORAGE_LOCATION_CONFIGURED)
}
//New permission granted, reload plugins
BackgroundService.RunCommand(this) { service: BackgroundService ->
val device = service.getDevice(mCurrentDevice)
device.reloadPluginsFromSettings()
}
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (DeviceHelper.KEY_DEVICE_NAME_PREFERENCE == key) {
mNavViewDeviceName.text = DeviceHelper.getDeviceName(this)
BackgroundService.RunCommand(this) { obj: BackgroundService -> obj.onNetworkChange() } //Re-send our identity packet
}
}
private fun uncheckAllMenuItems(menu: Menu) {
val size = menu.size()
for (i in 0 until size) {
val item = menu.getItem(i)
item.subMenu?.let { uncheckAllMenuItems(it) } ?: item.setChecked(false)
}
}
companion object {
const val EXTRA_DEVICE_ID = "deviceId"
const val PAIR_REQUEST_STATUS = "pair_req_status"
const val PAIRING_ACCEPTED = "accepted"
const val PAIRING_REJECTED = "rejected"
const val PAIRING_PENDING = "pending"
const val RESULT_NEEDS_RELOAD = RESULT_FIRST_USER
const val FLAG_FORCE_OVERVIEW = "forceOverview"
}
private inner class DrawerToggle(drawerLayout: DrawerLayout) : ActionBarDrawerToggle(
this, /* host Activity */
drawerLayout, /* DrawerLayout object */
R.string.open, /* "open drawer" description */
R.string.close /* "close drawer" description */
) {
override fun onDrawerClosed(drawerView: View) {
super.onDrawerClosed(drawerView)
closeDrawerCallback.isEnabled = false
}
override fun onDrawerOpened(drawerView: View) {
super.onDrawerOpened(drawerView)
closeDrawerCallback.isEnabled = true
}
}
}