mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-05 00:25:09 +00:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ed1dcdab52 | ||
|
e3ccfb1b25 | ||
|
4ae04ae060 | ||
|
801367458e | ||
|
26c72fae89 | ||
|
b981f9234b | ||
|
7f07e4658f | ||
|
f182d27ebe | ||
|
3b9d6ac61e | ||
|
6f24ac8d25 | ||
|
2891ec2092 | ||
|
fc1424c67c | ||
|
83efd6b355 | ||
|
05e14bb81c | ||
|
1236cbe4e3 | ||
|
5f9159a13f | ||
|
fbd9f8f216 | ||
|
e32b6b67e0 | ||
|
63a849b80a | ||
|
170bb5e717 | ||
|
f121e4982e | ||
|
937289730d | ||
|
624a9302fd | ||
|
e6f4b69464 | ||
|
2190c9cdaa | ||
|
864d44cb5b | ||
|
72e958a891 | ||
|
d4ab2ca6cf | ||
|
fd51ec7c14 | ||
|
28070954a6 | ||
|
e10f2496de | ||
|
95b4c08605 | ||
|
51d4de34c4 | ||
|
de2001bbe1 | ||
|
9c80cb9a40 | ||
|
0b03a66c37 | ||
|
6d66d69820 | ||
|
c0fc19baaa | ||
|
03ea5eae4c | ||
|
b373c28cdd |
@@ -28,23 +28,10 @@ plugins {
|
||||
|
||||
val licenseResDir = File("$projectDir/build/dependency-license-res")
|
||||
|
||||
fun String.runCommand(
|
||||
workingDir: File = File("."),
|
||||
timeoutAmount: Long = 60,
|
||||
timeoutUnit: TimeUnit = TimeUnit.SECONDS
|
||||
): String = ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`])[^'\"`]*\\1)*[^'\"`]*$)".toRegex()))
|
||||
.directory(workingDir)
|
||||
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||
.redirectError(ProcessBuilder.Redirect.PIPE)
|
||||
.start()
|
||||
.apply { waitFor(timeoutAmount, timeoutUnit) }
|
||||
.run {
|
||||
val error = errorStream.bufferedReader().readText().trim()
|
||||
if (error.isNotEmpty()) {
|
||||
throw Exception(error)
|
||||
}
|
||||
inputStream.bufferedReader().readText().trim()
|
||||
}
|
||||
val hashProvider = project.providers.exec {
|
||||
workingDir = rootDir
|
||||
commandLine("git", "rev-parse", "--short", "HEAD")
|
||||
}.standardOutput.asText.map { it.trim() }
|
||||
|
||||
android {
|
||||
namespace = "org.kde.kdeconnect_tp"
|
||||
@@ -53,8 +40,8 @@ android {
|
||||
applicationId = "org.kde.kdeconnect_tp"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 13302
|
||||
versionName = "1.33.2"
|
||||
versionCode = 13304
|
||||
versionName = "1.33.4"
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
buildFeatures {
|
||||
@@ -134,8 +121,7 @@ android {
|
||||
// Default output filename is "${project.name}-${v.name}.apk". We want
|
||||
// the Git commit short-hash to be added onto that default filename.
|
||||
try {
|
||||
val hash = "git rev-parse --short HEAD".runCommand(workingDir = rootDir)
|
||||
val newName = "${project.name}-${variant.name}-${hash}.apk"
|
||||
val newName = "${project.name}-${variant.name}-${hashProvider.get()}.apk"
|
||||
logger.quiet(" Found an output file ${output.outputFile.name}, renaming to $newName")
|
||||
output.outputFileName = newName
|
||||
} catch (ignored: Exception) {
|
||||
|
16
fastlane/metadata/android/en-US/changelogs/13303.txt
Normal file
16
fastlane/metadata/android/en-US/changelogs/13303.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
1.33.3
|
||||
* Fix more connection issues. Pairing again might be needed in some setups.
|
||||
* Add a setting to export the application logs.
|
||||
|
||||
1.33.2
|
||||
* Fix connection issues on some devices.
|
||||
|
||||
1.33.1
|
||||
* Fix compatibility with GSConnect.
|
||||
|
||||
1.33.0
|
||||
* Add support for PeerTube links.
|
||||
* Allow filtering notifications from work profile.
|
||||
* Fix bug where devices would unpair without user interaction.
|
||||
* Verification key now changes every second (only if both devices support it).
|
||||
* Fix crashes.
|
16
fastlane/metadata/android/en-US/changelogs/13304.txt
Normal file
16
fastlane/metadata/android/en-US/changelogs/13304.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
1.33.4
|
||||
* Extend offline URL sharing behavior to direct share targets.
|
||||
* Improve paring screen.
|
||||
|
||||
1.33.3
|
||||
* Fix connection issues. Pairing again might be needed in some cases.
|
||||
* Add a setting to export the application logs.
|
||||
|
||||
1.33.1
|
||||
* Fix compatibility with GSConnect.
|
||||
|
||||
1.33.0
|
||||
* Add support for PeerTube links.
|
||||
* Allow filtering notifications from work profile.
|
||||
* Fix bug where devices would unpair without user interaction.
|
||||
* Verification key now changes every second (if both devices support it).
|
@@ -1,33 +1,33 @@
|
||||
[versions]
|
||||
activityCompose = "1.10.1"
|
||||
androidDesugarJdkLibs = "2.1.5"
|
||||
androidGradlePlugin = "8.9.0"
|
||||
androidGradlePlugin = "8.9.2"
|
||||
androidSmsmms = "kdeconnect-1-21-0"
|
||||
appcompat = "1.7.0"
|
||||
bcpkixJdk15on = "1.70"
|
||||
classindexksp = "1.1"
|
||||
commonsCollections4 = "4.4"
|
||||
commonsIo = "2.18.0"
|
||||
classindexksp = "1.2"
|
||||
commonsCollections4 = "4.5.0"
|
||||
commonsIo = "2.19.0"
|
||||
commonsLang3 = "3.17.0"
|
||||
constraintlayoutCompose = "1.1.1"
|
||||
coreKtx = "1.15.0"
|
||||
dependencyLicenseReport = "2.7"
|
||||
coreKtx = "1.16.0"
|
||||
dependencyLicenseReport = "2.9"
|
||||
disklrucache = "2.0.2"
|
||||
documentfile = "1.0.1"
|
||||
gridlayout = "1.0.0"
|
||||
gridlayout = "1.1.0"
|
||||
jsonassert = "1.5.3"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.1.10"
|
||||
kspPlugin = "2.1.10-1.0.30"
|
||||
kotlinxCoroutinesCore = "1.10.1"
|
||||
kotlin = "2.1.20"
|
||||
kspPlugin = "2.1.20-1.0.32"
|
||||
kotlinxCoroutinesCore = "1.10.2"
|
||||
lifecycleExtensions = "2.2.0"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
logger = "1.0.3"
|
||||
material = "1.12.0"
|
||||
material3 = "1.3.1"
|
||||
material3 = "1.3.2"
|
||||
media = "1.7.0"
|
||||
minaCore = "2.2.4"
|
||||
mockitoCore = "5.16.0"
|
||||
mockitoCore = "5.17.0"
|
||||
preferenceKtx = "1.2.1"
|
||||
reactiveStreams = "1.0.4"
|
||||
recyclerview = "1.4.0"
|
||||
@@ -35,7 +35,7 @@ rxjava = "2.2.21"
|
||||
sl4j = "2.0.13"
|
||||
sshdCore = "2.15.0"
|
||||
swiperefreshlayout = "1.1.0"
|
||||
uiToolingPreview = "1.7.8"
|
||||
uiToolingPreview = "1.8.0"
|
||||
univocityParsers = "2.9.1"
|
||||
|
||||
[libraries]
|
||||
|
25
res/layout-v23/pairing_explanation_duplicate_names.xml
Normal file
25
res/layout-v23/pairing_explanation_duplicate_names.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:text="@string/pairing_duplicate_names"
|
||||
app:drawableStartCompat="@drawable/ic_warning"
|
||||
app:drawableTint="?attr/colorControlNormal">
|
||||
|
||||
</TextView>
|
25
res/layout/pairing_explanation_duplicate_names.xml
Normal file
25
res/layout/pairing_explanation_duplicate_names.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:text="@string/pairing_duplicate_names"
|
||||
app:drawableStartCompat="@drawable/ic_warning"
|
||||
app:drawableLeftCompat="@drawable/ic_warning">
|
||||
|
||||
</TextView>
|
@@ -19,12 +19,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pair_progress"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pair_message"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -33,6 +27,13 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
android:text="@string/device_not_paired"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pairing_explanation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dip"
|
||||
android:text="@string/pairing_explanation" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pair_verification"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -43,6 +44,12 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
app:drawableStartCompat="@drawable/ic_key" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pair_progress"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pair_button"
|
||||
android:layout_width="match_parent"
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Устройството е недостъпно</string>
|
||||
<string name="error_already_paired">Устройството вече е сдвоено</string>
|
||||
<string name="error_timed_out">Просрочка</string>
|
||||
<string name="error_clocks_not_match">Часовниците на устройствата не са синхронизирани</string>
|
||||
<string name="error_canceled_by_user">Отхвърлена от потребителя</string>
|
||||
<string name="error_canceled_by_other_peer">Отказана от другата страна</string>
|
||||
<string name="encryption_info_title">Информация за криптиране</string>
|
||||
@@ -192,6 +193,7 @@
|
||||
<string name="share_to">Споделяне към...</string>
|
||||
<string name="unreachable_device">%s (не е достъпно)</string>
|
||||
<string name="unreachable_device_url_share_text">URL адресите, споделени към недостъпно устройство, ще бъдат доставени до него, когато то стане достъпно.\n\n</string>
|
||||
<string name="protocol_version">Версия на протокол:</string>
|
||||
<string name="protocol_version_newer">Това устройство използва по-нова версия на протокола</string>
|
||||
<string name="plugin_settings_with_name">%s настройки</string>
|
||||
<string name="invalid_device_name">Невалидно име на устройство</string>
|
||||
@@ -307,6 +309,8 @@
|
||||
<string name="devices">Устройства</string>
|
||||
<string name="settings_rename">Име на устройство</string>
|
||||
<string name="settings_dark_mode">Тъмна тема</string>
|
||||
<string name="settings_export_logs">Експортиране на дневниците на KDE Connect</string>
|
||||
<string name="settings_export_logs_text">Генерирайте файл с информация за изпълнени команди, която може да помогне за отстраняването на проблеми.</string>
|
||||
<string name="settings_more_settings_title">Още настройки</string>
|
||||
<string name="settings_more_settings_text">Настройките за всяко устройство могат да бъдат намерени в раздел \"Настройки на плъгина\" в рамките на устройство.</string>
|
||||
<string name="setting_persistent_notification">Показване на постоянна нотификация</string>
|
||||
|
@@ -165,9 +165,11 @@
|
||||
<string name="middle_click">Envia un clic del mig</string>
|
||||
<string name="show_keyboard">Mostra el teclat</string>
|
||||
<string name="device_not_paired">El dispositiu no està aparellat</string>
|
||||
<string name="pairing_duplicate_names">Precaució: hi ha diversos dispositius amb el mateix nom.</string>
|
||||
<string name="request_pairing">Demana aparellar</string>
|
||||
<string name="pairing_accept">Accepta</string>
|
||||
<string name="pairing_reject">Rebutja</string>
|
||||
<string name="pairing_explanation">L\'aparellament de dos dispositius els donarà accés l\'un a l\'altre. Només aparelleu els vostres propis dispositius.</string>
|
||||
<string name="settings">Arranjament</string>
|
||||
<string name="mpris_play">Reprodueix</string>
|
||||
<string name="mpris_pause">Pausa</string>
|
||||
@@ -192,6 +194,7 @@
|
||||
<string name="mpris_notification_settings_summary">Permet controlar els reproductors multimèdia sense obrir el KDE Connect</string>
|
||||
<string name="share_to">Comparteix amb…</string>
|
||||
<string name="unreachable_device">%s (no s\'hi pot accedir)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">Els URL compartits amb un dispositiu no accessible es lliuraran un cop s\'hi pugui accedir.\n\n</string>
|
||||
<string name="protocol_version">Versió del protocol:</string>
|
||||
<string name="protocol_version_newer">Aquest dispositiu usa una versió nova del protocol</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="devices">Dispositius</string>
|
||||
<string name="settings_rename">Nom del dispositiu</string>
|
||||
<string name="settings_dark_mode">Tema fosc</string>
|
||||
<string name="settings_export_logs">Exporta els registres del KDE Connect</string>
|
||||
<string name="settings_export_logs_text">Genera un fitxer amb informació d\'execució que pot ajudar a resoldre problemes.</string>
|
||||
<string name="settings_more_settings_title">Més opcions</string>
|
||||
<string name="settings_more_settings_text">La configuració per dispositiu es pot trobar a «Arranjament dels connectors» des d\'un dispositiu.</string>
|
||||
<string name="setting_persistent_notification">Mostra les notificacions persistents</string>
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Kunhavigi la enhavon de la tondujo</string>
|
||||
<string name="pref_plugin_clipboard_sent">Clipboard Sendita</string>
|
||||
<string name="pref_plugin_mousepad">Fora enigo</string>
|
||||
<string name="pref_plugin_mousepad_desc">Uzu vian telefonon aŭ tablojdon kiel tuŝplaton kaj klavaron</string>
|
||||
<string name="pref_plugin_mousepad_desc">Uzu vian telefonon aŭ tabuleton kiel tuŝplaton kaj klavaron</string>
|
||||
<string name="pref_plugin_presenter">Prezento fora</string>
|
||||
<string name="pref_plugin_presenter_desc">Uzu vian aparaton por ŝanĝi lumbildojn en prezento</string>
|
||||
<string name="pref_plugin_remotekeyboard">Ricevi forajn klavojn</string>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string name="pref_plugin_mpris">Plurmedia regiloj</string>
|
||||
<string name="pref_plugin_mpris_desc">Provizas teleregilon por via plurmedia ludilo</string>
|
||||
<string name="pref_plugin_runcommand">Lanĉi Komandon</string>
|
||||
<string name="pref_plugin_runcommand_desc">Ekigi forajn komandojn de via telefono aŭ tablojdo</string>
|
||||
<string name="pref_plugin_runcommand_desc">Ekigi forajn komandojn de via telefono aŭ tabuleto</string>
|
||||
<string name="pref_plugin_contacts">Sinkronigilo de Kontaktoj</string>
|
||||
<string name="pref_plugin_contacts_desc">Permesi sinkronigi la kontaktlibron de la aparato</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Aparato ne atingebla</string>
|
||||
<string name="error_already_paired">Aparato jam parigita</string>
|
||||
<string name="error_timed_out">Tempo elĉerpita</string>
|
||||
<string name="error_clocks_not_match">Aparataj horloĝoj estas malsinkronaj</string>
|
||||
<string name="error_canceled_by_user">Nuligite de uzanto</string>
|
||||
<string name="error_canceled_by_other_peer">Nuligite de alia kunulo</string>
|
||||
<string name="encryption_info_title">Ĉifrada Informo</string>
|
||||
@@ -192,11 +193,13 @@
|
||||
<string name="share_to">Kunhavigi al…</string>
|
||||
<string name="unreachable_device">%s (Neatingebla)</string>
|
||||
<string name="unreachable_device_url_share_text">URL-oj kundividitaj al neatingebla aparato estos liveritaj al ĝi post kiam ĝi fariĝos atingebla.\n\n</string>
|
||||
<string name="protocol_version">Protokolversio:</string>
|
||||
<string name="protocol_version_newer">Ĉi tiu aparato uzas pli novan protokolversion</string>
|
||||
<string name="plugin_settings_with_name">%s agordoj</string>
|
||||
<string name="invalid_device_name">Nevalida aparato nomo</string>
|
||||
<string name="shareplugin_text_saved">Ricevita teksto, konservita en tondujo</string>
|
||||
<string name="custom_devices_settings">Propra aparato listo</string>
|
||||
<string name="custom_devices_settings_summary">%d aparatoj aldoniĝis permane</string>
|
||||
<string name="custom_device_list">Aldoni aparatojn per IP</string>
|
||||
<string name="custom_device_deleted">Propra aparato forigita</string>
|
||||
<string name="custom_device_list_help">Se via aparato ne estas aŭtomate detektita, vi povas aldoni ĝian IP-adreson aŭ gastigan nomon alklakante la Ŝveban Ago-Butonon</string>
|
||||
@@ -326,6 +329,7 @@
|
||||
<string name="empty_trusted_networks_list_text">Vi ankoraŭ ne aldonis neniun fidindan reton</string>
|
||||
<string name="allow_all_networks_text">Permesi ĉion</string>
|
||||
<string name="location_permission_needed_title">Permeso bezonata</string>
|
||||
<string name="bluetooth_permission_needed_desc">KDE Connect bezonas permeson por konekti al proksimaj aparatoj por fari aparatojn parigitaj per Bluetooth disponebla en KDE Connect.</string>
|
||||
<string name="location_permission_needed_desc">KDE Connect bezonas la fonlokan permeson por scii la WiFi-reton al kiu vi estas konektita eĉ kiam la programo estas en la fono. Ĉi tio estas ĉar la nomo de la WiFi-retoj ĉirkaŭ vi povus esti uzata por trovi vian lokon, eĉ kiam tion ne faras KDE Connect.</string>
|
||||
<string name="clipboard_android_x_incompat">Android 10 forigis aliron al tondujo al ĉiuj aplikaĵoj. Ĉi tiu kromaĵo estos malŝaltita.</string>
|
||||
<string name="mpris_open_url">Daŭre ludi ĉi tie</string>
|
||||
@@ -411,6 +415,7 @@
|
||||
<string name="tap_to_execute">Frapi por plenumi</string>
|
||||
<string name="plugin_stats">Statistiko de kromprogramoj</string>
|
||||
<string name="enable_udp_broadcast">Ebligi UDP-aparatan malkovron</string>
|
||||
<string name="enable_bluetooth">Ŝalti bluetooth (beta)</string>
|
||||
<string name="receive_notifications_permission_explanation">Sciigoj devas esti permesitaj ricevi ilin de aliaj aparatoj</string>
|
||||
<string name="findmyphone_notifications_explanation">La sciiga permeso estas necesa por ke la telefono povu sonori kiam la app estas en la fono</string>
|
||||
<string name="no_notifications">Sciigoj estas malŝaltitaj, vi ne ricevos alvenantajn parajn sciigojn.</string>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Laite tavoittamattomissa</string>
|
||||
<string name="error_already_paired">Laite on jo kytketty pariksi</string>
|
||||
<string name="error_timed_out">Aikakatkaisu</string>
|
||||
<string name="error_clocks_not_match">Laitteiden kelloja ei ole tahdistettu</string>
|
||||
<string name="error_canceled_by_user">Käyttäjä perui</string>
|
||||
<string name="error_canceled_by_other_peer">Vertaiskäyttäjä perui</string>
|
||||
<string name="encryption_info_title">Salaustiedot</string>
|
||||
@@ -192,6 +193,7 @@
|
||||
<string name="share_to">Jaa…</string>
|
||||
<string name="unreachable_device">%s (tavoittamattomissa)</string>
|
||||
<string name="unreachable_device_url_share_text">Tavoittamattomissa olevalle laitteelle jaetut verkko-osoitteet välitetään heti kun laite tavoitetaan.\n\n</string>
|
||||
<string name="protocol_version">Yhteyskäytäntöversio:</string>
|
||||
<string name="protocol_version_newer">Laite käyttää uudempaa yhteyskäytäntöversiota</string>
|
||||
<string name="plugin_settings_with_name">%s-asetukset</string>
|
||||
<string name="invalid_device_name">Virheellinen laitenimi</string>
|
||||
|
@@ -165,9 +165,11 @@
|
||||
<string name="middle_click">Envoyer un clic central</string>
|
||||
<string name="show_keyboard">Afficher le clavier</string>
|
||||
<string name="device_not_paired">Périphérique non associé</string>
|
||||
<string name="pairing_duplicate_names">Attention : Il existe plusieurs périphériques portant le même nom.</string>
|
||||
<string name="request_pairing">Demande d\'association</string>
|
||||
<string name="pairing_accept">Accepter</string>
|
||||
<string name="pairing_reject">Rejeter</string>
|
||||
<string name="pairing_explanation">L\'association de deux périphériques leur donnera un accès réciproque. Veuillez n\'associer que vos propres périphériques.</string>
|
||||
<string name="settings">Configuration</string>
|
||||
<string name="mpris_play">Lire</string>
|
||||
<string name="mpris_pause">Pause</string>
|
||||
@@ -192,6 +194,7 @@
|
||||
<string name="mpris_notification_settings_summary">Vous permet de contrôler vos lecteurs multimédia sans ouvrir KDEConnect.</string>
|
||||
<string name="share_to">Partager vers…</string>
|
||||
<string name="unreachable_device">%s (Inaccessible)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">Les URL partagées vers un appareil inaccessible lui seront transmises une fois qu\'il re-deviendra accessible.\n\n</string>
|
||||
<string name="protocol_version">Version de protocole :</string>
|
||||
<string name="protocol_version_newer">Le périphérique utilise une version plus récente du protocole</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="devices">Périphériques</string>
|
||||
<string name="settings_rename">Nom du périphérique</string>
|
||||
<string name="settings_dark_mode">Thème sombre</string>
|
||||
<string name="settings_export_logs">Exporter les journaux de KDEConnect</string>
|
||||
<string name="settings_export_logs_text">Générez un fichier avec des informations d\'exécution pouvant aider à résoudre les problèmes.</string>
|
||||
<string name="settings_more_settings_title">Plus de paramètres</string>
|
||||
<string name="settings_more_settings_text">Les paramètres par appareil sont disponibles dans la rubrique « Paramètres des modules externes » sur l\'appareil.</string>
|
||||
<string name="setting_persistent_notification">Afficher une notification persistante</string>
|
||||
|
@@ -185,11 +185,14 @@
|
||||
<string name="mpris_notification_settings_summary">Gerir kleift að stýra margmiðlunarspilurunum þínum án þess að opna KDE-tengingar</string>
|
||||
<string name="share_to">Deila með…</string>
|
||||
<string name="unreachable_device">%s (ekki aðgengilegt)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="protocol_version">Útgáfa samskiptamáta:</string>
|
||||
<string name="protocol_version_newer">Þetta tæki notar nýrri útgáfu samskiptastaðals</string>
|
||||
<string name="plugin_settings_with_name">Stillingar %s</string>
|
||||
<string name="invalid_device_name">Ógilt heiti tækis</string>
|
||||
<string name="shareplugin_text_saved">Tók við texta, vistaði á klippispjald</string>
|
||||
<string name="custom_devices_settings">Listi yfir sérsniðin tæki</string>
|
||||
<string name="custom_devices_settings_summary">%d tækjum bætt við handvirkt</string>
|
||||
<string name="custom_device_list">Bæta við tækjum eftir auðkennum</string>
|
||||
<string name="custom_device_deleted">Sérsniðnu tæki eytt</string>
|
||||
<string name="custom_device_fab_hint">Bæta við tæki</string>
|
||||
@@ -279,6 +282,7 @@
|
||||
<string name="devices">Tæki</string>
|
||||
<string name="settings_rename">Heiti tækis</string>
|
||||
<string name="settings_dark_mode">Dökkt þema</string>
|
||||
<string name="settings_export_logs">Flytja út atvikaskrár KDE Connect</string>
|
||||
<string name="settings_more_settings_title">Fleiri stillingar</string>
|
||||
<string name="setting_persistent_notification">Birta viðvarandi tilkynningu</string>
|
||||
<string name="setting_persistent_notification_oreo">Viðvarandi tilkynning</string>
|
||||
@@ -380,4 +384,7 @@
|
||||
<string name="mpris_keepwatching">Halda áfram spilun</string>
|
||||
<string name="mpris_keepwatching_settings_title">Halda áfram spilun</string>
|
||||
<string name="notification_channel_keepwatching">Halda áfram spilun</string>
|
||||
<string name="ping_failed">Gat ekki pikkað í tæki</string>
|
||||
<string name="device_host_invalid">Ógilt vélarheiti. Notaðu gilt vélarheiti, IPv4 eða IPv6</string>
|
||||
<string name="device_host_duplicate">Hýsivélin er nú þegar á listanum</string>
|
||||
</resources>
|
||||
|
@@ -165,9 +165,11 @@
|
||||
<string name="middle_click">Invia clic tasto centrale</string>
|
||||
<string name="show_keyboard">Mostra tastiera</string>
|
||||
<string name="device_not_paired">Dispositivo non associato</string>
|
||||
<string name="pairing_duplicate_names">Attenzione: ci sono più dispositivi con lo stesso nome.</string>
|
||||
<string name="request_pairing">Richiedi associazione</string>
|
||||
<string name="pairing_accept">Accetta</string>
|
||||
<string name="pairing_reject">Rifiuta</string>
|
||||
<string name="pairing_explanation">L\'associazione di due dispositivi consentirà loro l\'accesso reciproco. Associa solo i tuoi dispositivi.</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="mpris_play">Riproduci</string>
|
||||
<string name="mpris_pause">Pausa</string>
|
||||
@@ -192,6 +194,7 @@
|
||||
<string name="mpris_notification_settings_summary">Consenti di controllare i lettori multimediali senza aprire KDE Connect</string>
|
||||
<string name="share_to">Condividi con…</string>
|
||||
<string name="unreachable_device">%s (non raggiungibile)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">Gli URL condivisi su un dispositivo irraggiungibile saranno recapitati una volta che sarà tornato raggiungibile.\n\n</string>
|
||||
<string name="protocol_version">Versione del protocollo:</string>
|
||||
<string name="protocol_version_newer">Questo dispositivo usa una nuova versione del protocollo di rete</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="devices">Dispositivi</string>
|
||||
<string name="settings_rename">Nome dispositivo</string>
|
||||
<string name="settings_dark_mode">Tema scuro</string>
|
||||
<string name="settings_export_logs">Esporta i registri di KDE Connect</string>
|
||||
<string name="settings_export_logs_text">Genera un file con informazioni di esecuzione che possono aiutare a risolvere i problemi.</string>
|
||||
<string name="settings_more_settings_title">Altre impostazioni</string>
|
||||
<string name="settings_more_settings_text">Le impostazioni per dispositivo sono disponibili sotto «Impostazioni estensioni» dall\'interno del dispositivo.</string>
|
||||
<string name="setting_persistent_notification">Mostra notifica persistente</string>
|
||||
|
@@ -181,9 +181,11 @@
|
||||
<string name="middle_click">שליחת לחיצה אמצעית (גלגלת)</string>
|
||||
<string name="show_keyboard">הצגת מקלדת</string>
|
||||
<string name="device_not_paired">המכשיר לא מצומד</string>
|
||||
<string name="pairing_duplicate_names">אזהרה: יש כמה מכשירים שונים באותו השם.</string>
|
||||
<string name="request_pairing">בקשת צימוד</string>
|
||||
<string name="pairing_accept">אישור</string>
|
||||
<string name="pairing_reject">דחייה</string>
|
||||
<string name="pairing_explanation">צימוד שני מכשירים יעניק להם גישה מאחד לשני ולהפך. יש לצמד רק אם המכשירים הם שלך.</string>
|
||||
<string name="settings">הגדרות</string>
|
||||
<string name="mpris_play">נגינה</string>
|
||||
<string name="mpris_pause">השהיה</string>
|
||||
@@ -208,6 +210,7 @@
|
||||
<string name="mpris_notification_settings_summary">לאפשר שליטה בנגני המדיה שלך מבלי לפתוח את KDE Connect</string>
|
||||
<string name="share_to">שיתוף אל…</string>
|
||||
<string name="unreachable_device">%s (לא נגיש)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">כתובות שותפו להתקן בלתי נגיש והן תועברנה אליו ברגע שישוב להיות נגיש.\n\n</string>
|
||||
<string name="protocol_version">גרסת פרוטוקול:</string>
|
||||
<string name="protocol_version_newer">המכשיר משתמש בגרסה חדשה יותר</string>
|
||||
@@ -325,6 +328,8 @@
|
||||
<string name="devices">מכשירים</string>
|
||||
<string name="settings_rename">שם המכשיר</string>
|
||||
<string name="settings_dark_mode">ערכת עיצוב כהה</string>
|
||||
<string name="settings_export_logs">ייצוא היומנים של KDE Connect</string>
|
||||
<string name="settings_export_logs_text">ליצור קובץ עם פרטי הפעלה שיכול לסייע בפתרון תקלות.</string>
|
||||
<string name="settings_more_settings_title">הגדרות נוספות</string>
|
||||
<string name="settings_more_settings_text">אפשר למצוא הגדרות נקודתיות למכשיר תחת ‚הגדרות תוסף’ מתוך המכשיר.</string>
|
||||
<string name="setting_persistent_notification">הצגת התראה קבועה</string>
|
||||
|
@@ -89,15 +89,12 @@
|
||||
<string name="error_not_reachable">მოწყობილობა მიუწვდოელია</string>
|
||||
<string name="error_already_paired">მოწყობილობა უკვე დაწყვილებულია</string>
|
||||
<string name="error_timed_out">ვადა გავიდა</string>
|
||||
<string name="error_clocks_not_match">მოწყობილობის საათების სინქრონიზაცია დარღვეულია</string>
|
||||
<string name="error_canceled_by_user">გაუქმებულია მომხმარებლის მიერ</string>
|
||||
<string name="error_canceled_by_other_peer">გაუქმებულია პარტნიორის მიერ</string>
|
||||
<string name="encryption_info_title">დაშიფვრის ინფორმაცია</string>
|
||||
<string name="pair_requested">დაწყვილების მოთხოვნა</string>
|
||||
<string name="pair_succeeded">დაწყვილება წარმატებულია</string>
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="one">File: %1s</item>
|
||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">ფაილი: %1$s</item>
|
||||
<item quantity="other">(ფაილი %2$d of %3$d) : %1$s</item>
|
||||
@@ -134,6 +131,7 @@
|
||||
</string-array>
|
||||
<string name="share_to">გაზიარება…</string>
|
||||
<string name="unreachable_device">%s (მიუწვდომელია)</string>
|
||||
<string name="protocol_version">პროტოკოლის ვერსია:</string>
|
||||
<string name="protocol_version_newer">მოწყობილობა პროტოკოლის უფრო ახალ ვერსიას იყენებს</string>
|
||||
<string name="plugin_settings_with_name">%s-ის მორგება</string>
|
||||
<string name="invalid_device_name">მოწყობილობის არასწორი სახელი</string>
|
||||
@@ -217,6 +215,7 @@
|
||||
<string name="devices">მოწყობილობები</string>
|
||||
<string name="settings_rename">მოწყობილობის სახელი</string>
|
||||
<string name="settings_dark_mode">ბნელი თემა</string>
|
||||
<string name="settings_export_logs">KDE Connect-ის ჟურნალის გატანა</string>
|
||||
<string name="settings_more_settings_title">მეტი პარამეტრი</string>
|
||||
<string name="extra_options">დამატებითი პარამეტრები</string>
|
||||
<string name="privacy_options">კონფიდენციალობის პარამეტრები</string>
|
||||
@@ -292,4 +291,5 @@
|
||||
<string name="send_clipboard">ბუფერის გაგზავნა</string>
|
||||
<string name="tap_to_execute">ქმედების შესასრულებლად დაატყაპუნეთ</string>
|
||||
<string name="plugin_stats">მოდულის სტატისტიკა</string>
|
||||
<string name="ping_failed">მოწყობილობა არ იპინგება</string>
|
||||
</resources>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">장치에 접근할 수 없음</string>
|
||||
<string name="error_already_paired">장치가 이미 페어링됨</string>
|
||||
<string name="error_timed_out">시간 초과됨</string>
|
||||
<string name="error_clocks_not_match">장치 시계가 맞지 않음</string>
|
||||
<string name="error_canceled_by_user">사용자가 취소함</string>
|
||||
<string name="error_canceled_by_other_peer">다른 쪽에서 취소함</string>
|
||||
<string name="encryption_info_title">암호화 정보</string>
|
||||
@@ -156,9 +157,11 @@
|
||||
<string name="middle_click">가운데 단추 클릭 신호 보내기</string>
|
||||
<string name="show_keyboard">키보드 표시</string>
|
||||
<string name="device_not_paired">장치가 페어링되지 않음</string>
|
||||
<string name="pairing_duplicate_names">경고: 이름이 같은 장치가 여러 대 있습니다.</string>
|
||||
<string name="request_pairing">페어링 요청</string>
|
||||
<string name="pairing_accept">수락</string>
|
||||
<string name="pairing_reject">거부</string>
|
||||
<string name="pairing_explanation">두 장치를 페어링하면 서로간의 접근을 허용합니다. 내 장치끼리만 페어링하십시오.</string>
|
||||
<string name="settings">설정</string>
|
||||
<string name="mpris_play">재생</string>
|
||||
<string name="mpris_pause">일시 정지</string>
|
||||
@@ -183,12 +186,15 @@
|
||||
<string name="mpris_notification_settings_summary">KDE Connect를 열지 않고 미디어 재생기 제어</string>
|
||||
<string name="share_to">다음으로 공유…</string>
|
||||
<string name="unreachable_device">%s(접근할 수 없음)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s(✕)</string>
|
||||
<string name="unreachable_device_url_share_text">접근할 수 없는 장치와 공유한 URL은 장치에 다시 접근할 수 있게 될 때 전달됩니다.\n\n</string>
|
||||
<string name="protocol_version">프로토콜 버전:</string>
|
||||
<string name="protocol_version_newer">이 장치의 프로토콜 버전이 더 새롭습니다</string>
|
||||
<string name="plugin_settings_with_name">%s 설정</string>
|
||||
<string name="invalid_device_name">잘못된 장치 이름</string>
|
||||
<string name="shareplugin_text_saved">텍스트 수신, 클립보드에 복사됨</string>
|
||||
<string name="custom_devices_settings">사용자 정의 장치 목록</string>
|
||||
<string name="custom_devices_settings_summary">장치 %d개를 수동으로 추가함</string>
|
||||
<string name="custom_device_list">IP로 장치 추가</string>
|
||||
<string name="custom_device_deleted">사용자 정의 장치 삭제됨</string>
|
||||
<string name="custom_device_list_help">장치가 자동으로 감지되지 않았다면 떠 다니는 동작 단추를 클릭해서 IP 주소나 호스트 이름으로 장치를 수동으로 추가할 수 있습니다</string>
|
||||
@@ -298,6 +304,8 @@
|
||||
<string name="devices">장치</string>
|
||||
<string name="settings_rename">장치 이름</string>
|
||||
<string name="settings_dark_mode">어두운 테마</string>
|
||||
<string name="settings_export_logs">KDE Connect 로그 내보내기</string>
|
||||
<string name="settings_export_logs_text">문제 해결을 위해서 실행 정보를 포함하는 파일을 생성합니다.</string>
|
||||
<string name="settings_more_settings_title">더 많은 설정</string>
|
||||
<string name="settings_more_settings_text">장치별 설정은 각각 장치의 \'플러그인 설정\'에서 확인할 수 있습니다.</string>
|
||||
<string name="setting_persistent_notification">항상 표시되는 알림 표시</string>
|
||||
|
@@ -165,9 +165,11 @@
|
||||
<string name="middle_click">Verstuur een middelste muisklik</string>
|
||||
<string name="show_keyboard">Toetsenbord tonen</string>
|
||||
<string name="device_not_paired">Apparaat is niet gepaard</string>
|
||||
<string name="pairing_duplicate_names">Voorzichtig: er zijn meerdere apparaten met dezelfde naam.</string>
|
||||
<string name="request_pairing">Verzoek voor maken van paar</string>
|
||||
<string name="pairing_accept">Accepteren</string>
|
||||
<string name="pairing_reject">Afwijzen</string>
|
||||
<string name="pairing_explanation">Twee apparaten paren zal ze toegang geven tot elkaar. Paar alleen uw eigen apparaten.</string>
|
||||
<string name="settings">Instellingen</string>
|
||||
<string name="mpris_play">Afspelen</string>
|
||||
<string name="mpris_pause">Pauzeren</string>
|
||||
@@ -192,6 +194,7 @@
|
||||
<string name="mpris_notification_settings_summary">Staat besturing van uw mediaspelers toe zonder KDE Connect te openen</string>
|
||||
<string name="share_to">Delen met…</string>
|
||||
<string name="unreachable_device">%s (niet bereikbaar)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">URL\'s gedeeld met een niet bereikbaar apparaat zullen er afgeleverd worden wanneer deze bereikbaar wordt.\n\n</string>
|
||||
<string name="protocol_version">Protocolversie:</string>
|
||||
<string name="protocol_version_newer">Dit apparaat gebruikt een nieuwere protocolversie</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="devices">Apparaten</string>
|
||||
<string name="settings_rename">Apparaatnaam</string>
|
||||
<string name="settings_dark_mode">Donker thema</string>
|
||||
<string name="settings_export_logs">Logs van KDE Connect exporteren</string>
|
||||
<string name="settings_export_logs_text">Genereert een bestand met uitvoeringsinformatie die kan helpen problemen op te lossen.</string>
|
||||
<string name="settings_more_settings_title">Meer instellingen</string>
|
||||
<string name="settings_more_settings_text">Instellingen per apparaat kunnen gevonden worden onder \'Plug-in-instellingen\' vanuit een apparaat.</string>
|
||||
<string name="setting_persistent_notification">Blijvende melding tonen</string>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Urządzenie nieosiągalne</string>
|
||||
<string name="error_already_paired">Urządzenie już sparowano</string>
|
||||
<string name="error_timed_out">Upłynął czas na odpowiedź</string>
|
||||
<string name="error_clocks_not_match">Zegary urządzenia nie są zsynchronizowane</string>
|
||||
<string name="error_canceled_by_user">Użytkownik zaniechał</string>
|
||||
<string name="error_canceled_by_other_peer">Inny uczestnik zaniechał</string>
|
||||
<string name="encryption_info_title">Dane o szyfrowaniu</string>
|
||||
@@ -208,6 +209,7 @@
|
||||
<string name="share_to">Udostępnij urządzeniu...</string>
|
||||
<string name="unreachable_device">%s (nieosiągalne)</string>
|
||||
<string name="unreachable_device_url_share_text">Adres URL udostępniony nieosiągalnemu urządzeniu zostanie do niego dostarczony zaraz po tym jak stanie się osiągalne.\n\n</string>
|
||||
<string name="protocol_version">Wersja protokołu:</string>
|
||||
<string name="protocol_version_newer">Urządzenie to używa nowszej wersji protokołu</string>
|
||||
<string name="plugin_settings_with_name">Ustawienia %s</string>
|
||||
<string name="invalid_device_name">Nieprawidłowa nazwa urządzenia</string>
|
||||
|
@@ -181,9 +181,11 @@
|
||||
<string name="middle_click">Pošlji sredinski klik</string>
|
||||
<string name="show_keyboard">Pokaži tipkovnico</string>
|
||||
<string name="device_not_paired">Naprava ni uparjena</string>
|
||||
<string name="pairing_duplicate_names">POZOR: Obstaja več naprav z istim imenom.</string>
|
||||
<string name="request_pairing">Zahtevaj uparjanje</string>
|
||||
<string name="pairing_accept">Sprejmi</string>
|
||||
<string name="pairing_reject">Zavrni</string>
|
||||
<string name="pairing_explanation">Uparjanje dveh naprav jim bo omogočilo dostop ene do druge. Uparjajte samo svoje lastne naprave.</string>
|
||||
<string name="settings">Nastavitve</string>
|
||||
<string name="mpris_play">Predvajaj</string>
|
||||
<string name="mpris_pause">Premor</string>
|
||||
@@ -208,6 +210,7 @@
|
||||
<string name="mpris_notification_settings_summary">Dovoli nadzor nad predvajalniki medijev, ne da bi odprli KDE Connect</string>
|
||||
<string name="share_to">Deli z…</string>
|
||||
<string name="unreachable_device">%s (Nedosegljiva)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">URL-ji, ki so v skupni rabi z nedosegljivo napravo, ji bodo dostavljeni, ko postane dosegljiva.\n\n</string>
|
||||
<string name="protocol_version">Protokol različice:</string>
|
||||
<string name="protocol_version_newer">Ta naprava uporablja novejšo različico protokola</string>
|
||||
@@ -325,6 +328,8 @@
|
||||
<string name="devices">Naprave</string>
|
||||
<string name="settings_rename">Ime naprave</string>
|
||||
<string name="settings_dark_mode">Temna tema</string>
|
||||
<string name="settings_export_logs">Izvozi dnevnike KDE Connect</string>
|
||||
<string name="settings_export_logs_text">Ustvari datoteko z informacijami o izvajanju, ki lahko pomagajo pri odpravljanju težav.</string>
|
||||
<string name="settings_more_settings_title">Več nastavitev</string>
|
||||
<string name="settings_more_settings_text">Nastavitve posamezne naprave najdete v razdelku \'Nastavitve vtičnikov\' v napravi.</string>
|
||||
<string name="setting_persistent_notification">Prikazuj trajno obvestilo</string>
|
||||
|
@@ -130,7 +130,7 @@
|
||||
</plurals>
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="one">Dosya: %1s</item>
|
||||
<item quantity="other">(Dosya %2$d %3$d) : %1$s</item>
|
||||
<item quantity="other">(Dosya %2$d/%3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">%1$d dosya %2$s gönderiliyor</item>
|
||||
@@ -165,9 +165,11 @@
|
||||
<string name="middle_click">Orta Tık Gönder</string>
|
||||
<string name="show_keyboard">Klavyeyi Göster</string>
|
||||
<string name="device_not_paired">Aygıt eşleşmemiş</string>
|
||||
<string name="pairing_duplicate_names">Dikkat: Aynı adlı birden çok aygıt var.</string>
|
||||
<string name="request_pairing">Eşleşme isteği</string>
|
||||
<string name="pairing_accept">Onayla</string>
|
||||
<string name="pairing_reject">Reddet</string>
|
||||
<string name="pairing_explanation">İki aygıtı eşleştirmek, onlara birbirlerine erişim hakkını verecektir. Yalnızca kendi aygıtlarınızı eşleştirin.</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="mpris_play">Oynat</string>
|
||||
<string name="mpris_pause">Duraklat</string>
|
||||
@@ -192,6 +194,7 @@
|
||||
<string name="mpris_notification_settings_summary">KDE Bağlan’ı açmadan ortam oynatıcılarınızı denetlemenize izin verin</string>
|
||||
<string name="share_to">Şuraya Paylaş…</string>
|
||||
<string name="unreachable_device">%s (Erişilebilir değil)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">Erişilemeyen bir aygıta gönderilen URL’ler, aygıt erişilebilir olduğunda teslim edilir.\n\n</string>
|
||||
<string name="protocol_version">Protokol sürümü:</string>
|
||||
<string name="protocol_version_newer">Bu aygıt, daha yeni bir protokol sürümü kullanıyor</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="devices">Aygıtlar</string>
|
||||
<string name="settings_rename">Aygıt adı</string>
|
||||
<string name="settings_dark_mode">Karanlık tema</string>
|
||||
<string name="settings_export_logs">KDE Bağlan Günlüklerini Dışa Aktar</string>
|
||||
<string name="settings_export_logs_text">Sorun tanılama konusunda yandımcı olabilecek yürütme bilgisini içeren bir dosya üretin.</string>
|
||||
<string name="settings_more_settings_title">Daha fazla ayar</string>
|
||||
<string name="settings_more_settings_text">Aygıt başına ayarlar, bir aygıt içinden “Eklenti Ayarları” altında bulunabilir.</string>
|
||||
<string name="setting_persistent_notification">Kalıcı bildirim göster</string>
|
||||
@@ -427,5 +432,5 @@
|
||||
<string name="ping_failed">Aygıt pinglenemedi</string>
|
||||
<string name="ping_in_progress">Pingleniyor…</string>
|
||||
<string name="device_host_invalid">Makine geçersiz. Geçerli bir makine adı kullanın; IPv4 veya IPv6 gibi</string>
|
||||
<string name="device_host_duplicate">Listede makine halihazırda var</string>
|
||||
<string name="device_host_duplicate">Makine listede halihazırda var</string>
|
||||
</resources>
|
||||
|
@@ -181,9 +181,11 @@
|
||||
<string name="middle_click">Надіслати клацання лівою кнопкою</string>
|
||||
<string name="show_keyboard">Показати клавіатуру</string>
|
||||
<string name="device_not_paired">Пристрій не пов’язано</string>
|
||||
<string name="pairing_duplicate_names">Попередження: маємо декілька пристроїв із однією назвою.</string>
|
||||
<string name="request_pairing">Надіслати запит щодо пов’язування</string>
|
||||
<string name="pairing_accept">Прийняти</string>
|
||||
<string name="pairing_reject">Відмовити</string>
|
||||
<string name="pairing_explanation">Пов\'язування двох пристроїв надасть їм взаємний доступ. Пов\'язуйте між собою лише ваші власні пристрої.</string>
|
||||
<string name="settings">Параметри</string>
|
||||
<string name="mpris_play">Пуск</string>
|
||||
<string name="mpris_pause">Пауза</string>
|
||||
@@ -208,6 +210,7 @@
|
||||
<string name="mpris_notification_settings_summary">Уможливлює керування відтворенням мультимедійних даних без відкриття KDE Connect</string>
|
||||
<string name="share_to">Оприлюднити на…</string>
|
||||
<string name="unreachable_device">%s (недоступний)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">Адреси, які оприлюднено на недоступному пристрої, буде надіслано, щойно пристрій стане доступним.\n\n</string>
|
||||
<string name="protocol_version">Версія протоколу:</string>
|
||||
<string name="protocol_version_newer">На цьому пристрої використовується новіша версія протоколу</string>
|
||||
@@ -325,6 +328,8 @@
|
||||
<string name="devices">Пристрої</string>
|
||||
<string name="settings_rename">Назва пристрою</string>
|
||||
<string name="settings_dark_mode">Темна тема</string>
|
||||
<string name="settings_export_logs">Експортувати журнал KDE Connect</string>
|
||||
<string name="settings_export_logs_text">Створити файл з даними щодо виконання, які можуть допомогти у діагностиці вад.</string>
|
||||
<string name="settings_more_settings_title">Додаткові параметри</string>
|
||||
<string name="settings_more_settings_text">Окремі параметри пристроїв наведено на сторінці «Параметри додатків» сторінки пристрою.</string>
|
||||
<string name="setting_persistent_notification">Показувати постійне сповіщення</string>
|
||||
|
@@ -157,9 +157,11 @@
|
||||
<string name="middle_click">发送中键点击</string>
|
||||
<string name="show_keyboard">显示键盘</string>
|
||||
<string name="device_not_paired">设备未配对</string>
|
||||
<string name="pairing_duplicate_names">注意:存在多个同名设备。</string>
|
||||
<string name="request_pairing">请求配对</string>
|
||||
<string name="pairing_accept">接受</string>
|
||||
<string name="pairing_reject">拒绝</string>
|
||||
<string name="pairing_explanation">两个设备在配对后即可相互访问。请只配对您自己的设备。</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="mpris_play">播放</string>
|
||||
<string name="mpris_pause">暂停</string>
|
||||
@@ -184,6 +186,7 @@
|
||||
<string name="mpris_notification_settings_summary">不打开 KDE Connect 也能在常驻通知中控制媒体播放器</string>
|
||||
<string name="share_to">分享到…</string>
|
||||
<string name="unreachable_device">%s (无法访问)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">URL 被分享到了不可访问的设备。它将在设备能够访问时自动传输到该设备。\n</string>
|
||||
<string name="protocol_version">协议版本:</string>
|
||||
<string name="protocol_version_newer">此设备使用较新版本的协议</string>
|
||||
@@ -301,6 +304,8 @@
|
||||
<string name="devices">设备</string>
|
||||
<string name="settings_rename">设备名</string>
|
||||
<string name="settings_dark_mode">深色主题</string>
|
||||
<string name="settings_export_logs">导出 KDE Connect 日志</string>
|
||||
<string name="settings_export_logs_text">生成一个包含程序执行信息的文件,有助于排查故障。</string>
|
||||
<string name="settings_more_settings_title">更多设置</string>
|
||||
<string name="settings_more_settings_text">每个设备的独立设置可以在设备页内的“插件设置”下找到。</string>
|
||||
<string name="setting_persistent_notification">启用常驻通知栏</string>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">裝置無法存取</string>
|
||||
<string name="error_already_paired">裝置已經配對</string>
|
||||
<string name="error_timed_out">逾時</string>
|
||||
<string name="error_clocks_not_match">裝置時鐘不同步</string>
|
||||
<string name="error_canceled_by_user">使用者中斷</string>
|
||||
<string name="error_canceled_by_other_peer">被其他同等功能應用中斷</string>
|
||||
<string name="encryption_info_title">加密資訊</string>
|
||||
@@ -184,6 +185,7 @@
|
||||
<string name="share_to">分享給…</string>
|
||||
<string name="unreachable_device">%s(無法存取)</string>
|
||||
<string name="unreachable_device_url_share_text">若分享網址 (URL) 到無法存取的裝置,將在該裝置變得可存取後再傳送過去。\n\n</string>
|
||||
<string name="protocol_version">協定版本:</string>
|
||||
<string name="protocol_version_newer">此裝置使用較新的通訊協定版本</string>
|
||||
<string name="plugin_settings_with_name">%s的設定</string>
|
||||
<string name="invalid_device_name">無效的裝置名稱</string>
|
||||
@@ -299,6 +301,8 @@
|
||||
<string name="devices">裝置</string>
|
||||
<string name="settings_rename">裝置名稱</string>
|
||||
<string name="settings_dark_mode">暗色主題</string>
|
||||
<string name="settings_export_logs">匯出 KDE Connect 紀錄</string>
|
||||
<string name="settings_export_logs_text">產生包含執行資訊的檔案來幫助發現問題。</string>
|
||||
<string name="settings_more_settings_title">更多設定</string>
|
||||
<string name="settings_more_settings_text">各裝置設定可在裝置內的「外掛程式設定」底下找到。</string>
|
||||
<string name="setting_persistent_notification">顯示一致設定</string>
|
||||
|
@@ -227,9 +227,11 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
<string name="middle_click">Send Middle Click</string>
|
||||
<string name="show_keyboard">Show Keyboard</string>
|
||||
<string name="device_not_paired">Device not paired</string>
|
||||
<string name="pairing_duplicate_names">Caution: There are multiple devices with the same name.</string>
|
||||
<string name="request_pairing">Request pairing</string>
|
||||
<string name="pairing_accept">Accept</string>
|
||||
<string name="pairing_reject">Reject</string>
|
||||
<string name="pairing_explanation">Pairing two devices will give them access to each other. Only pair together your own devices.</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="mpris_play">Play</string>
|
||||
<string name="mpris_pause">Pause</string>
|
||||
@@ -264,6 +266,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
<string name="mpris_notification_key" translatable="false">mpris_notification_enabled</string>
|
||||
<string name="share_to">Share to…</string>
|
||||
<string name="unreachable_device">%s (Unreachable)</string>
|
||||
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
|
||||
<string name="unreachable_device_url_share_text">URLs shared to an unreachable device will be delivered to it once it becomes reachable.\n\n</string>
|
||||
<string name="protocol_version">Protocol version:</string>
|
||||
<string name="protocol_version_newer">This device uses a newer protocol version</string>
|
||||
@@ -401,6 +404,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
|
||||
<string name="settings_rename">Device name</string>
|
||||
<string name="settings_dark_mode">Dark theme</string>
|
||||
<string name="settings_export_logs">Export KDE Connect logs</string>
|
||||
<string name="settings_export_logs_text">Generate a file with execution information that can help troubleshoot issues.</string>
|
||||
|
||||
<string name="settings_more_settings_title">More settings</string>
|
||||
<string name="settings_more_settings_text">Per-device settings can be found under \'Plugin settings\' from within a device.</string>
|
||||
<string name="setting_persistent_notification">Show persistent notification</string>
|
||||
|
@@ -24,13 +24,16 @@ import java.io.Reader
|
||||
import java.util.UUID
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input: InputStream, output: OutputStream, remoteAddress: BluetoothDevice, deviceInfo: DeviceInfo, linkProvider: BluetoothLinkProvider) : BaseLink(context!!, linkProvider) {
|
||||
private val connection: ConnectionMultiplexer?
|
||||
private val input: InputStream
|
||||
private val output: OutputStream
|
||||
private val remoteAddress: BluetoothDevice
|
||||
private val linkProvider: BluetoothLinkProvider
|
||||
private val deviceInfo: DeviceInfo
|
||||
class BluetoothLink(
|
||||
context: Context?,
|
||||
connection: ConnectionMultiplexer,
|
||||
val input: InputStream,
|
||||
val output: OutputStream,
|
||||
val remoteAddress: BluetoothDevice,
|
||||
val theDeviceInfo: DeviceInfo,
|
||||
val linkProvider: BluetoothLinkProvider
|
||||
) : BaseLink(context!!, linkProvider) {
|
||||
private val connection: ConnectionMultiplexer? = connection
|
||||
private var continueAccepting = true
|
||||
private val receivingThread = Thread(object : Runnable {
|
||||
override fun run() {
|
||||
@@ -64,8 +67,7 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
|
||||
private fun processMessage(message: String) {
|
||||
val np: NetworkPacket
|
||||
np = try {
|
||||
val np = try {
|
||||
NetworkPacket.unserialize(message)
|
||||
} catch (e: JSONException) {
|
||||
Log.e("BluetoothLink/receiving", "Unable to parse message.", e)
|
||||
@@ -84,15 +86,6 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
})
|
||||
|
||||
init {
|
||||
this.connection = connection
|
||||
this.input = input
|
||||
this.output = output
|
||||
this.deviceInfo = deviceInfo
|
||||
this.remoteAddress = remoteAddress
|
||||
this.linkProvider = linkProvider
|
||||
}
|
||||
|
||||
fun startListening() {
|
||||
receivingThread.start()
|
||||
}
|
||||
@@ -102,7 +95,7 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
|
||||
override fun getDeviceInfo(): DeviceInfo {
|
||||
return deviceInfo
|
||||
return theDeviceInfo
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
|
@@ -35,6 +35,7 @@ import java.io.Reader
|
||||
import java.security.cert.CertificateException
|
||||
import java.util.UUID
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
import androidx.core.content.edit
|
||||
|
||||
class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
private val visibleDevices: MutableMap<String, BluetoothLink> = HashMap()
|
||||
@@ -138,9 +139,9 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
} catch (e: SecurityException) {
|
||||
Log.e("KDEConnect", "Security Exception for CONNECT", e)
|
||||
|
||||
val prefenceEditor = PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
prefenceEditor.putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)
|
||||
prefenceEditor.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
private class ChannelInputStream constructor(val channel: Channel) : InputStream(), Closeable {
|
||||
private class ChannelInputStream(val channel: Channel) : InputStream(), Closeable {
|
||||
override fun available(): Int {
|
||||
return channel.available()
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private class ChannelOutputStream constructor(val channel: Channel) : OutputStream(), Closeable {
|
||||
private class ChannelOutputStream(val channel: Channel) : OutputStream(), Closeable {
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
channel.close()
|
||||
@@ -78,7 +78,7 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private class Channel constructor(val multiplexer: ConnectionMultiplexer, val id: UUID) : Closeable {
|
||||
private class Channel(val multiplexer: ConnectionMultiplexer, val id: UUID) : Closeable {
|
||||
val readBuffer: ByteBuffer = ByteBuffer.allocate(BUFFER_SIZE)
|
||||
val lock = ReentrantLock()
|
||||
var lockCondition: Condition = lock.newCondition()
|
||||
@@ -371,14 +371,9 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ListenRunnable constructor(socket: BluetoothSocket) : Runnable {
|
||||
var input: InputStream
|
||||
var output: OutputStream
|
||||
|
||||
init {
|
||||
input = socket.inputStream
|
||||
output = socket.outputStream
|
||||
}
|
||||
private inner class ListenRunnable(socket: BluetoothSocket) : Runnable {
|
||||
var input: InputStream = socket.inputStream
|
||||
var output: OutputStream = socket.outputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readBuffer(buffer: ByteArray, len: Int) {
|
||||
|
@@ -10,8 +10,8 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Network;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
@@ -27,7 +27,6 @@ import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||
import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsFragment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -68,13 +67,15 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
final static int MAX_IDENTITY_PACKET_SIZE = 1024 * 512;
|
||||
final static int MAX_UDP_PACKET_SIZE = 1024 * 512;
|
||||
|
||||
final static long MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE = 500L;
|
||||
final static long MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE = 1000L;
|
||||
|
||||
private final Context context;
|
||||
|
||||
final HashMap<String, LanLink> visibleDevices = new HashMap<>(); // Links by device id
|
||||
|
||||
final ConcurrentHashMap<String, Long> lastConnectionTime = new ConcurrentHashMap<>();
|
||||
final static int MAX_RATE_LIMIT_ENTRIES = 255;
|
||||
final ConcurrentHashMap<String, Long> lastConnectionTimeByDeviceId = new ConcurrentHashMap<>();
|
||||
final ConcurrentHashMap<InetAddress, Long> lastConnectionTimeByIp = new ConcurrentHashMap<>();
|
||||
|
||||
private ServerSocket tcpServer;
|
||||
private DatagramSocket udpServer;
|
||||
@@ -92,29 +93,70 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
super.onConnectionLost(link);
|
||||
}
|
||||
|
||||
Pair<NetworkPacket, Boolean> unserializeReceivedIdentityPacket(String message) {
|
||||
NetworkPacket identityPacket;
|
||||
try {
|
||||
identityPacket = NetworkPacket.unserialize(message);
|
||||
} catch (JSONException e) {
|
||||
Log.w("KDE/LanLinkProvider", "Invalid identity packet received: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!DeviceInfo.isValidIdentityPacket(identityPacket)) {
|
||||
Log.w("KDE/LanLinkProvider", "Invalid identity packet received.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final String deviceId = identityPacket.getString("deviceId");
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (deviceId.equals(myId)) {
|
||||
//Ignore my own broadcast
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rateLimitByDeviceId(deviceId)) {
|
||||
Log.i("LanLinkProvider", "Discarding second packet from the same device " + deviceId + " received too quickly");
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean deviceTrusted = isDeviceTrusted(deviceId);
|
||||
if (!deviceTrusted && !TrustedNetworkHelper.isTrustedNetwork(context)) {
|
||||
Log.i("KDE/LanLinkProvider", "Ignoring identity packet because the device is not trusted and I'm not on a trusted network.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Pair<>(identityPacket, deviceTrusted);
|
||||
}
|
||||
|
||||
//They received my UDP broadcast and are connecting to me. The first thing they send should be their identity packet.
|
||||
@WorkerThread
|
||||
private void tcpPacketReceived(Socket socket) throws IOException {
|
||||
|
||||
NetworkPacket networkPacket;
|
||||
InetAddress address = socket.getInetAddress();
|
||||
if (rateLimitByIp(address)) {
|
||||
Log.i("LanLinkProvider", "Discarding second TCP packet from the same ip " + address + " received too quickly");
|
||||
return;
|
||||
}
|
||||
|
||||
String message;
|
||||
try {
|
||||
String message = readSingleLine(socket);
|
||||
networkPacket = NetworkPacket.unserialize(message);
|
||||
//Log.e("TcpListener", "Received TCP packet: " + networkPacket.serialize());
|
||||
message = readSingleLine(socket);
|
||||
//Log.e("TcpListener", "Received TCP packet: " + identityPacket.serialize());
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Exception while receiving TCP packet", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "identity packet received from a TCP connection from " + networkPacket.getString("deviceName"));
|
||||
|
||||
boolean deviceTrusted = isDeviceTrusted(networkPacket.getString("deviceId"));
|
||||
if (!deviceTrusted && !TrustedNetworkHelper.isTrustedNetwork(context)) {
|
||||
Log.i("KDE/LanLinkProvider", "Ignoring identity packet because the device is not trusted and I'm not on a trusted network.");
|
||||
final Pair<NetworkPacket, Boolean> pair = unserializeReceivedIdentityPacket(message);
|
||||
if (pair == null) {
|
||||
return;
|
||||
}
|
||||
final NetworkPacket identityPacket = pair.first;
|
||||
final boolean deviceTrusted = pair.second;
|
||||
|
||||
identityPacketReceived(networkPacket, socket, LanLink.ConnectionStarted.Locally, deviceTrusted);
|
||||
Log.i("KDE/LanLinkProvider", "identity packet received from a TCP connection from " + identityPacket.getString("deviceName"));
|
||||
|
||||
identityPacketReceived(identityPacket, socket, LanLink.ConnectionStarted.Locally, deviceTrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,40 +178,53 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
throw new IOException("Couldn't read a line from the socket");
|
||||
}
|
||||
|
||||
boolean rateLimitByIp(InetAddress address) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = lastConnectionTimeByIp.get(address);
|
||||
if (last != null && (last + MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE > now)) {
|
||||
return true;
|
||||
}
|
||||
lastConnectionTimeByIp.put(address, now);
|
||||
if (lastConnectionTimeByIp.size() > MAX_RATE_LIMIT_ENTRIES) {
|
||||
lastConnectionTimeByIp.entrySet().removeIf(e -> e.getValue() + MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE < now);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean rateLimitByDeviceId(String deviceId) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = lastConnectionTimeByDeviceId.get(deviceId);
|
||||
if (last != null && (last + MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE > now)) {
|
||||
return true;
|
||||
}
|
||||
lastConnectionTimeByDeviceId.put(deviceId, now);
|
||||
if (lastConnectionTimeByDeviceId.size() > MAX_RATE_LIMIT_ENTRIES) {
|
||||
lastConnectionTimeByDeviceId.entrySet().removeIf(e -> e.getValue() + MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE < now);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//I've received their broadcast and should connect to their TCP socket and send my identity.
|
||||
@WorkerThread
|
||||
private void udpPacketReceived(DatagramPacket packet) throws JSONException, IOException {
|
||||
|
||||
final InetAddress address = packet.getAddress();
|
||||
|
||||
if (rateLimitByIp(address)) {
|
||||
Log.i("LanLinkProvider", "Discarding second UDP packet from the same ip " + address + " received too quickly");
|
||||
return;
|
||||
}
|
||||
|
||||
String message = new String(packet.getData(), Charsets.UTF_8);
|
||||
final NetworkPacket identityPacket;
|
||||
try {
|
||||
identityPacket = NetworkPacket.unserialize(message);
|
||||
} catch (JSONException e) {
|
||||
Log.w("KDE/LanLinkProvider", "Invalid identity packet received: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DeviceInfo.isValidIdentityPacket(identityPacket)) {
|
||||
Log.w("KDE/LanLinkProvider", "Invalid identity packet received.");
|
||||
final Pair<NetworkPacket, Boolean> pair = unserializeReceivedIdentityPacket(message);
|
||||
if (pair == null) {
|
||||
return;
|
||||
}
|
||||
final NetworkPacket identityPacket = pair.first;
|
||||
final boolean deviceTrusted = pair.second;
|
||||
|
||||
final String deviceId = identityPacket.getString("deviceId");
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (deviceId.equals(myId)) {
|
||||
//Ignore my own broadcast
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = lastConnectionTime.get(deviceId);
|
||||
if (last != null && (last + MILLIS_DELAY_BETWEEN_CONNECTIONS_TO_SAME_DEVICE > now)) {
|
||||
Log.i("LanLinkProvider", "Discarding second UDP packet from the same device " + deviceId + " received too quickly");
|
||||
return;
|
||||
}
|
||||
lastConnectionTime.put(deviceId, now);
|
||||
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
|
||||
|
||||
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
|
||||
if (tcpPort < MIN_PORT || tcpPort > MAX_PORT) {
|
||||
@@ -177,14 +232,6 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
|
||||
|
||||
boolean deviceTrusted = isDeviceTrusted(identityPacket.getString("deviceId"));
|
||||
if (!deviceTrusted && !TrustedNetworkHelper.isTrustedNetwork(context)) {
|
||||
Log.i("KDE/LanLinkProvider", "Ignoring identity packet because the device is not trusted and I'm not on a trusted network.");
|
||||
return;
|
||||
}
|
||||
|
||||
SocketFactory socketFactory = SocketFactory.getDefault();
|
||||
Socket socket = socketFactory.createSocket(address, tcpPort);
|
||||
configureSocket(socket);
|
||||
@@ -224,18 +271,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
*/
|
||||
@WorkerThread
|
||||
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted, final boolean deviceTrusted) throws IOException {
|
||||
|
||||
if (!DeviceInfo.isValidIdentityPacket(identityPacket)) {
|
||||
Log.w("KDE/LanLinkProvider", "Invalid identity packet received.");
|
||||
return;
|
||||
}
|
||||
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
final String deviceId = identityPacket.getString("deviceId");
|
||||
if (deviceId.equals(myId)) {
|
||||
Log.e("KDE/LanLinkProvider", "Somehow I'm connected to myself, ignoring. This should not happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
int protocolVersion = identityPacket.getInt("protocolVersion");
|
||||
if (deviceTrusted && isProtocolDowngrade(deviceId, protocolVersion)) {
|
||||
@@ -420,12 +456,6 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
}
|
||||
|
||||
private void broadcastUdpIdentityPacket(@Nullable Network network) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!preferences.getBoolean(SettingsFragment.KEY_UDP_BROADCAST_ENABLED, true)) {
|
||||
Log.i("LanLinkProvider", "UDP broadcast is disabled in settings. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.execute(() -> {
|
||||
List<DeviceHost> hostList = CustomDevicesActivity
|
||||
.getCustomDeviceList(context);
|
||||
|
@@ -41,7 +41,7 @@ public class MdnsDiscovery {
|
||||
this.lanLinkProvider = lanLinkProvider;
|
||||
this.mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||
this.mNsdResolveQueue = new NsdResolveQueue(this.mNsdManager);
|
||||
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
multicastLock = wifiManager.createMulticastLock("kdeConnectMdnsMulticastLock");
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class MdnsDiscovery {
|
||||
// Also, on Android Lollipop those fields aren't resolved.
|
||||
String deviceName = DeviceHelper.getDeviceName(context);
|
||||
String deviceType = DeviceHelper.getDeviceType().toString();
|
||||
String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion);
|
||||
String protocolVersion = Integer.toString(DeviceHelper.PROTOCOL_VERSION);
|
||||
serviceInfo.setAttribute("id", deviceId);
|
||||
serviceInfo.setAttribute("name", deviceName);
|
||||
serviceInfo.setAttribute("type", deviceType);
|
||||
|
@@ -32,7 +32,6 @@ import org.kde.kdeconnect.Backends.BaseLinkProvider
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider.ConnectionReceiver
|
||||
import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider
|
||||
import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity
|
||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandActivity
|
||||
|
@@ -33,7 +33,6 @@ import org.kde.kdeconnect.DeviceStats.countReceived
|
||||
import org.kde.kdeconnect.DeviceStats.countSent
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||
import org.kde.kdeconnect.PairingHandler.PairingCallback
|
||||
import org.kde.kdeconnect.Plugins.Plugin
|
||||
import org.kde.kdeconnect.Plugins.Plugin.Companion.getPluginKey
|
||||
@@ -46,6 +45,7 @@ import java.util.Vector
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import androidx.core.content.edit
|
||||
|
||||
class Device : PacketReceiver {
|
||||
|
||||
@@ -164,7 +164,7 @@ class Device : PacketReceiver {
|
||||
|
||||
// Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
|
||||
fun compareProtocolVersion(): Int =
|
||||
deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion
|
||||
deviceInfo.protocolVersion - DeviceHelper.PROTOCOL_VERSION
|
||||
|
||||
val isPaired: Boolean
|
||||
get() = pairingHandler.state == PairingHandler.PairState.Paired
|
||||
@@ -209,7 +209,7 @@ class Device : PacketReceiver {
|
||||
|
||||
// Store as trusted device
|
||||
val preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE)
|
||||
preferences.edit().putBoolean(deviceInfo.id, true).apply()
|
||||
preferences.edit { putBoolean(deviceInfo.id, true) }
|
||||
|
||||
try {
|
||||
reloadPluginsFromSettings()
|
||||
@@ -228,10 +228,10 @@ class Device : PacketReceiver {
|
||||
override fun unpaired() {
|
||||
Log.i("Device", "unpaired, removing from trusted devices list")
|
||||
val preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE)
|
||||
preferences.edit().remove(deviceInfo.id).apply()
|
||||
preferences.edit { remove(deviceInfo.id) }
|
||||
|
||||
val devicePreferences = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE)
|
||||
devicePreferences.edit().clear().apply()
|
||||
devicePreferences.edit { clear() }
|
||||
|
||||
pairingCallbacks.forEach(PairingCallback::unpaired)
|
||||
|
||||
@@ -596,7 +596,7 @@ class Device : PacketReceiver {
|
||||
}
|
||||
|
||||
fun setPluginEnabled(pluginKey: String, value: Boolean) {
|
||||
settings.edit().putBoolean(pluginKey, value).apply()
|
||||
settings.edit { putBoolean(pluginKey, value) }
|
||||
reloadPluginsFromSettings()
|
||||
}
|
||||
|
||||
|
@@ -59,12 +59,12 @@ class DeviceInfo(
|
||||
*/
|
||||
fun toIdentityPacket(): NetworkPacket =
|
||||
NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY).also { np ->
|
||||
np.set("deviceId", id)
|
||||
np.set("deviceName", name)
|
||||
np.set("protocolVersion", protocolVersion)
|
||||
np.set("deviceType", type.toString())
|
||||
np.set("incomingCapabilities", incomingCapabilities!!)
|
||||
np.set("outgoingCapabilities", outgoingCapabilities!!)
|
||||
np["deviceId"] = id
|
||||
np["deviceName"] = name
|
||||
np["protocolVersion"] = protocolVersion
|
||||
np["deviceType"] = type.toString()
|
||||
np["incomingCapabilities"] = incomingCapabilities!!
|
||||
np["outgoingCapabilities"] = outgoingCapabilities!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -107,10 +107,10 @@ class DeviceInfo(
|
||||
fun isValidIdentityPacket(identityPacket: NetworkPacket): Boolean = with(identityPacket) {
|
||||
type == NetworkPacket.PACKET_TYPE_IDENTITY &&
|
||||
DeviceHelper.filterName(getString("deviceName", "")).isNotBlank() &&
|
||||
isValidDeviceId(getString("deviceId", ""));
|
||||
isValidDeviceId(getString("deviceId", ""))
|
||||
}
|
||||
|
||||
private val DEVICE_ID_REGEX = "^[a-zA-Z0-9_-]{32,38}\$".toRegex()
|
||||
private val DEVICE_ID_REGEX = "^[a-zA-Z0-9_-]{32,38}$".toRegex()
|
||||
|
||||
@JvmStatic
|
||||
fun isValidDeviceId(deviceId: String): Boolean = deviceId.matches(DEVICE_ID_REGEX)
|
||||
|
@@ -115,7 +115,7 @@ object DeviceStats {
|
||||
val entry = iterator.next()
|
||||
val events = entry.value
|
||||
|
||||
var index = Collections.binarySearch(events, cutoutTimestamp)
|
||||
var index = events.binarySearch(cutoutTimestamp)
|
||||
if (index < 0) {
|
||||
index = -(index + 1) // Convert the negative index to insertion point
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ public final class CollectionsBackport {
|
||||
}
|
||||
|
||||
static boolean eq(Object o1, Object o2) {
|
||||
return o1 == null ? o2 == null : o1.equals(o2);
|
||||
return Objects.equals(o1, o2);
|
||||
}
|
||||
|
||||
static class UnmodifiableNavigableSetBackport<E>
|
||||
|
@@ -214,7 +214,7 @@ public class ContactsHelper {
|
||||
|
||||
Map<uID, Map<String, String>> databaseValue = accessContactsDatabase(context, projection, selection, selectionArgs, null);
|
||||
|
||||
if (databaseValue.size() == 0) {
|
||||
if (databaseValue.isEmpty()) {
|
||||
throw new ContactNotFoundException("Querying for contact with id " + contactID + " returned no results.");
|
||||
}
|
||||
|
||||
|
27
src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
Normal file
27
src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.kde.kdeconnect.Helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
data class CreateFileParams(
|
||||
val fileMimeType: String,
|
||||
val suggestedFileName: String,
|
||||
)
|
||||
|
||||
class CreateFileResultContract : ActivityResultContract<CreateFileParams, Uri?>() {
|
||||
|
||||
override fun createIntent(context: Context, input: CreateFileParams): Intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
setTypeAndNormalize(input.fileMimeType)
|
||||
putExtra(Intent.EXTRA_TITLE, input.suggestedFileName)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? = when (resultCode) {
|
||||
Activity.RESULT_OK -> intent?.data
|
||||
else -> null
|
||||
}
|
||||
}
|
@@ -11,7 +11,6 @@ import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import com.univocity.parsers.common.TextParsingException
|
||||
import com.univocity.parsers.csv.CsvParser
|
||||
@@ -26,9 +25,10 @@ import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
import androidx.core.content.edit
|
||||
|
||||
object DeviceHelper {
|
||||
const val ProtocolVersion = 8
|
||||
const val PROTOCOL_VERSION = 8
|
||||
|
||||
const val KEY_DEVICE_NAME_PREFERENCE = "device_name_preference"
|
||||
private const val KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference"
|
||||
@@ -85,7 +85,7 @@ object DeviceHelper {
|
||||
|
||||
// If we get here we managed to download the file. Mark that as done so we don't try again even if we don't end up finding a name.
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply()
|
||||
preferences.edit { putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true) }
|
||||
|
||||
BufferedReader(
|
||||
InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16)
|
||||
@@ -124,26 +124,17 @@ object DeviceHelper {
|
||||
fun setDeviceName(context: Context, name: String) {
|
||||
val filteredName = filterName(name)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, filteredName).apply()
|
||||
preferences.edit { putString(KEY_DEVICE_NAME_PREFERENCE, filteredName) }
|
||||
}
|
||||
|
||||
fun initializeDeviceId(context: Context) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val preferenceKeys: Set<String> = preferences.all.keys
|
||||
if (preferenceKeys.contains(KEY_DEVICE_ID_PREFERENCE)) {
|
||||
val deviceId = preferences.getString(KEY_DEVICE_ID_PREFERENCE, "")!!
|
||||
if (DeviceInfo.isValidDeviceId(deviceId)) {
|
||||
return // We already have an ID
|
||||
}
|
||||
@SuppressLint("HardwareIds")
|
||||
val deviceName = if (preferenceKeys.isEmpty()) {
|
||||
// For new installations, use random IDs
|
||||
Log.i("DeviceHelper","No device ID found and this looks like a new installation, creating a random ID")
|
||||
UUID.randomUUID().toString().replace('-', '_')
|
||||
} else {
|
||||
// Use the ANDROID_ID as device ID for existing installations, for backwards compatibility
|
||||
Log.i("DeviceHelper", "No device ID found but this seems an existing installation, using the Android ID")
|
||||
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
}
|
||||
preferences.edit().putString(KEY_DEVICE_ID_PREFERENCE, deviceName).apply()
|
||||
val deviceName = UUID.randomUUID().toString().replace("-", "")
|
||||
preferences.edit { putString(KEY_DEVICE_ID_PREFERENCE, deviceName) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -159,12 +150,12 @@ object DeviceHelper {
|
||||
SslHelper.certificate,
|
||||
getDeviceName(context),
|
||||
deviceType,
|
||||
ProtocolVersion,
|
||||
PROTOCOL_VERSION,
|
||||
PluginFactory.incomingCapabilities,
|
||||
PluginFactory.outgoingCapabilities
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun filterName(input: String): String = input.replace(NAME_INVALID_CHARACTERS_REGEX, "").take(MAX_DEVICE_NAME_LENGTH)
|
||||
fun filterName(input: String): String = input.replace(NAME_INVALID_CHARACTERS_REGEX, "").trim().take(MAX_DEVICE_NAME_LENGTH)
|
||||
}
|
||||
|
@@ -97,7 +97,7 @@ object FilesHelper {
|
||||
|
||||
fun contentResolverExtract(): Triple<String?, Long, Long?> {
|
||||
// Since we used Intent.CATEGORY_OPENABLE, these two columns are the only ones we are guaranteed to have: https://developer.android.com/reference/android/provider/OpenableColumns
|
||||
val proj = arrayOf(OpenableColumns.SIZE, OpenableColumns.DISPLAY_NAME,)
|
||||
val proj = arrayOf(OpenableColumns.SIZE, OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
try {
|
||||
contentResolver.query(uri, proj, null, null, null).use { cursor ->
|
||||
|
@@ -11,7 +11,6 @@ import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.media.ThumbnailUtils
|
||||
import android.net.Uri
|
||||
@@ -23,6 +22,8 @@ import android.telephony.TelephonyManager
|
||||
import android.util.Log
|
||||
import android.util.Pair
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.mms.pdu_alt.MultimediaMessagePdu
|
||||
import com.google.android.mms.pdu_alt.PduPersister
|
||||
import com.google.android.mms.util_alt.PduCache
|
||||
@@ -37,13 +38,11 @@ import org.kde.kdeconnect.Helpers.TelephonyHelper.LocalPhoneNumber
|
||||
import org.kde.kdeconnect.Plugins.SMSPlugin.MimeType
|
||||
import org.kde.kdeconnect.Plugins.SMSPlugin.SmsMmsUtils
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
import java.util.Objects
|
||||
import java.util.SortedMap
|
||||
import java.util.TreeMap
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@@ -52,7 +51,7 @@ object SMSHelper {
|
||||
private const val THUMBNAIL_WIDTH = 100
|
||||
|
||||
// The constant Telephony.Mms.Part.CONTENT_URI was added in API 29
|
||||
val mMSPartUri : Uri = Uri.parse("content://mms/part/")
|
||||
val mMSPartUri : Uri = "content://mms/part/".toUri()
|
||||
|
||||
/**
|
||||
* Get the base address for all message conversations
|
||||
@@ -67,14 +66,14 @@ object SMSHelper {
|
||||
if ("Samsung".equals(Build.MANUFACTURER, ignoreCase = true)) {
|
||||
Log.i("SMSHelper", "This appears to be a Samsung device. This may cause some features to not work properly.")
|
||||
}
|
||||
return Uri.parse("content://mms-sms/conversations?simple=true")
|
||||
return "content://mms-sms/conversations?simple=true".toUri()
|
||||
}
|
||||
|
||||
private fun getCompleteConversationsUri(): Uri {
|
||||
// This glorious - but completely undocumented - content URI gives us all messages, both MMS and SMS,
|
||||
// in all conversations
|
||||
// See https://stackoverflow.com/a/36439630/3723163
|
||||
return Uri.parse("content://mms-sms/complete-conversations")
|
||||
return "content://mms-sms/complete-conversations".toUri()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,12 +650,7 @@ object SMSHelper {
|
||||
)
|
||||
val videoThumbnail = retriever.frameAtTime
|
||||
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(
|
||||
Bitmap.createScaledBitmap(
|
||||
videoThumbnail!!,
|
||||
THUMBNAIL_WIDTH,
|
||||
THUMBNAIL_HEIGHT,
|
||||
true
|
||||
)
|
||||
videoThumbnail!!.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
||||
)
|
||||
attachments.add(
|
||||
Attachment(
|
||||
|
@@ -24,7 +24,6 @@ import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.kde.kdeconnect.KdeConnect;
|
||||
|
@@ -6,7 +6,6 @@
|
||||
package org.kde.kdeconnect.Helpers
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
|
||||
object StorageHelper {
|
||||
fun getDisplayName(treeUri: Uri): String {
|
||||
|
@@ -13,6 +13,7 @@ import android.net.wifi.WifiManager
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
|
||||
class TrustedNetworkHelper(private val context: Context) {
|
||||
|
||||
@@ -22,19 +23,19 @@ class TrustedNetworkHelper(private val context: Context) {
|
||||
return serializedNetworks.split(NETWORK_SSID_DELIMITER, "#_#" /* TODO remove old delimiter in 2025 */).filter { it.isNotEmpty() }
|
||||
}
|
||||
set(value) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putString(KEY_CUSTOM_TRUSTED_NETWORKS, value.joinToString(NETWORK_SSID_DELIMITER))
|
||||
.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putString(
|
||||
KEY_CUSTOM_TRUSTED_NETWORKS,
|
||||
value.joinToString(NETWORK_SSID_DELIMITER)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var allNetworksAllowed: Boolean
|
||||
get() = !hasPermissions || PreferenceManager.getDefaultSharedPreferences(context).getBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, true)
|
||||
set(value) = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, value)
|
||||
.apply()
|
||||
set(value) = PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, value)
|
||||
}
|
||||
|
||||
val hasPermissions: Boolean
|
||||
get() = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||
|
@@ -25,6 +25,7 @@ import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import androidx.core.content.edit
|
||||
|
||||
/*
|
||||
* This class holds all the active devices and makes them accessible from every other class.
|
||||
@@ -116,7 +117,7 @@ class KdeConnect : Application() {
|
||||
"KdeConnect",
|
||||
"Couldn't load the certificate for a remembered device. Removing from trusted list.", e
|
||||
)
|
||||
preferences.edit().remove(it).apply()
|
||||
preferences.edit { remove(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +128,7 @@ class KdeConnect : Application() {
|
||||
trustedDevices.filter { preferences.getBoolean(it, false) }
|
||||
.forEach {
|
||||
Log.d("KdeConnect", "Removing devices: $it")
|
||||
preferences.edit().remove(it).apply()
|
||||
preferences.edit { remove(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,9 +11,7 @@ import org.json.JSONObject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.net.Socket
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
class NetworkPacket private constructor(
|
||||
val type: String,
|
||||
|
@@ -25,8 +25,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class BigscreenPlugin extends Plugin {
|
||||
|
||||
|
@@ -25,8 +25,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class ClipboardPlugin extends Plugin {
|
||||
|
||||
|
@@ -37,7 +37,6 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class FindMyPhonePlugin extends Plugin {
|
||||
|
@@ -19,8 +19,6 @@ import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class MousePadPlugin extends Plugin {
|
||||
|
||||
|
@@ -157,9 +157,6 @@ public class PointerAccelerationProfileFactory {
|
||||
|
||||
public static PointerAccelerationProfile getProfileWithName(String name) {
|
||||
switch (name) {
|
||||
case "noacceleration":
|
||||
default:
|
||||
return new DefaultProfile();
|
||||
case "weaker":
|
||||
return new PolynomialProfile(0.25f);
|
||||
case "weak":
|
||||
@@ -170,6 +167,9 @@ public class PointerAccelerationProfileFactory {
|
||||
return new PolynomialProfile(1.5f);
|
||||
case "stronger":
|
||||
return new PolynomialProfile(2.0f);
|
||||
case "noacceleration":
|
||||
default:
|
||||
return new DefaultProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ import java.net.URL
|
||||
import java.net.URLDecoder
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import androidx.core.net.toUri
|
||||
|
||||
/**
|
||||
* Handles the cache for album art
|
||||
@@ -130,7 +131,7 @@ internal object AlbumArtCache {
|
||||
if (albumUrl.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
val url = Uri.parse(albumUrl)
|
||||
val url = albumUrl.toUri()
|
||||
|
||||
//We currently only support http(s), file, and kdeconnect urls
|
||||
if (url.scheme !in ALLOWED_SCHEMES) {
|
||||
@@ -221,7 +222,7 @@ internal object AlbumArtCache {
|
||||
* Does the actual fetching and makes sure only not too many fetches are running at the same time
|
||||
*/
|
||||
private fun initiateFetch() {
|
||||
var url : Uri;
|
||||
var url : Uri
|
||||
synchronized(fetchUrlList) {
|
||||
if (numFetching >= 2 || fetchUrlList.isEmpty()) return
|
||||
//Fetch the last-requested url first, it will probably be needed first
|
||||
@@ -283,7 +284,7 @@ internal object AlbumArtCache {
|
||||
payload.close()
|
||||
return
|
||||
}
|
||||
val url = Uri.parse(albumUrl)
|
||||
val url = albumUrl.toUri()
|
||||
if (url.scheme !in REMOTE_FETCH_SCHEMES) {
|
||||
//Shouldn't happen (checked on receival of the url), but just to be sure
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "Got invalid art url with payload: $albumUrl")
|
||||
|
@@ -34,6 +34,7 @@ import org.kde.kdeconnect_tp.databinding.MprisNowPlayingBinding
|
||||
import java.net.MalformedURLException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private typealias MprisPlayerCallback = (MprisPlayer) -> Unit
|
||||
|
||||
@@ -370,7 +371,7 @@ class MprisNowPlayingFragment : Fragment(), VolumeKeyListener {
|
||||
if (targetPlayer != null && item.itemId == MENU_OPEN_URL) {
|
||||
try {
|
||||
val url = VideoUrlsHelper.formatUriWithSeek(targetPlayer.url, targetPlayer.position).toString()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(browserIntent)
|
||||
targetPlayer.sendPause()
|
||||
return true
|
||||
|
@@ -33,6 +33,7 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import java.net.MalformedURLException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@LoadablePlugin
|
||||
class MprisPlugin : Plugin() {
|
||||
@@ -273,7 +274,7 @@ class MprisPlugin : Plugin() {
|
||||
playerStatus.isGoPreviousAllowed = np.getBoolean("canGoPrevious", playerStatus.isGoPreviousAllowed)
|
||||
playerStatus.seekAllowed = np.getBoolean("canSeek", playerStatus.seekAllowed)
|
||||
val newAlbumArtUrlString = np.getString("albumArtUrl", playerStatus.albumArtUrl)
|
||||
val newAlbumArtUrl = Uri.parse(newAlbumArtUrlString)
|
||||
val newAlbumArtUrl = newAlbumArtUrlString.toUri()
|
||||
if (newAlbumArtUrl.scheme in AlbumArtCache.ALLOWED_SCHEMES) {
|
||||
playerStatus.albumArtUrl = newAlbumArtUrl.toString()
|
||||
} else {
|
||||
@@ -334,7 +335,7 @@ class MprisPlugin : Plugin() {
|
||||
) {
|
||||
try {
|
||||
val url = VideoUrlsHelper.formatUriWithSeek(playerStatus.url, playerStatus.position).toString()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, browserIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
val notificationManager = ContextCompat.getSystemService(context, NotificationManager::class.java)
|
||||
@@ -350,7 +351,7 @@ class MprisPlugin : Plugin() {
|
||||
builder.build()
|
||||
)
|
||||
} catch (e: MalformedURLException) {
|
||||
e.printStackTrace();
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.content.getSystemService
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.NetworkPacket
|
||||
|
@@ -24,8 +24,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class PresenterPlugin extends Plugin {
|
||||
|
||||
|
@@ -29,7 +29,6 @@ import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class ReceiveNotificationsPlugin extends Plugin {
|
||||
|
@@ -67,7 +67,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
}
|
||||
|
||||
public static boolean isConnected() {
|
||||
return instances.size() > 0;
|
||||
return !instances.isEmpty();
|
||||
}
|
||||
|
||||
private static final SparseIntArray specialKeyMap = new SparseIntArray();
|
||||
@@ -135,7 +135,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
try {
|
||||
if (instances.contains(this)) {
|
||||
instances.remove(this);
|
||||
if (instances.size() < 1 && RemoteKeyboardService.instance != null)
|
||||
if (instances.isEmpty() && RemoteKeyboardService.instance != null)
|
||||
RemoteKeyboardService.instance.handler.post(() -> RemoteKeyboardService.instance.updateInputView());
|
||||
}
|
||||
} finally {
|
||||
@@ -348,7 +348,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
public enum MousePadPacketType {
|
||||
Keyboard,
|
||||
Mouse,
|
||||
};
|
||||
}
|
||||
|
||||
public static MousePadPacketType getMousePadPacketType(NetworkPacket np) {
|
||||
if (np.has("key") || np.has("specialKey")) {
|
||||
|
@@ -165,7 +165,7 @@ public class RemoteKeyboardService
|
||||
intent.putExtra(MainActivity.FLAG_FORCE_OVERVIEW, true);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
if (instances.size() < 1)
|
||||
if (instances.isEmpty())
|
||||
Toast.makeText(this, R.string.remotekeyboard_not_connected, Toast.LENGTH_SHORT).show();
|
||||
else // instances.size() > 1
|
||||
Toast.makeText(this, R.string.remotekeyboard_multiple_connections, Toast.LENGTH_SHORT).show();
|
||||
|
@@ -130,7 +130,7 @@ public class RunCommandActivity extends BaseActivity<ActivityRunCommandBinding>
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
if (item.getItemId() == R.id.copy_url_to_clipboard) {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(info.position);
|
||||
CommandEntry entry = commandItems.get(info.position);
|
||||
String url = "kdeconnect://runcommand/" + deviceId + "/" + entry.getKey();
|
||||
ClipboardManager cm = ContextCompat.getSystemService(this, ClipboardManager.class);
|
||||
cm.setText(url);
|
||||
|
@@ -34,7 +34,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class RunCommandPlugin extends Plugin {
|
||||
|
@@ -18,12 +18,12 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import org.kde.kdeconnect.Device
|
||||
import org.kde.kdeconnect.KdeConnect
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.WidgetRemoteCommandPluginDialogBinding
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.streams.toList
|
||||
|
||||
class RunCommandWidgetConfigActivity : AppCompatActivity() {
|
||||
|
||||
@@ -45,7 +45,7 @@ class RunCommandWidgetConfigActivity : AppCompatActivity() {
|
||||
val binding = WidgetRemoteCommandPluginDialogBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val pairedDevices = KdeConnect.getInstance().devices.values.stream().filter(Device::isPaired).collect(Collectors.toList());
|
||||
val pairedDevices = KdeConnect.getInstance().devices.values.stream().filter(Device::isPaired).collect(Collectors.toList())
|
||||
|
||||
binding.runCommandsDeviceList.adapter = object : ArrayAdapter<Device>(this, 0, pairedDevices) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
@@ -76,9 +76,9 @@ private const val PREFS_NAME = "org.kde.kdeconnect_tp.WidgetProvider"
|
||||
private const val PREF_PREFIX_KEY = "appwidget_"
|
||||
|
||||
internal fun saveWidgetDeviceIdPref(context: Context, appWidgetId: Int, deviceName: String) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||
prefs.putString(PREF_PREFIX_KEY + appWidgetId, deviceName)
|
||||
prefs.apply()
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||
putString(PREF_PREFIX_KEY + appWidgetId, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadWidgetDeviceIdPref(context: Context, appWidgetId: Int): String? {
|
||||
@@ -87,7 +87,7 @@ internal fun loadWidgetDeviceIdPref(context: Context, appWidgetId: Int): String?
|
||||
}
|
||||
|
||||
internal fun deleteWidgetDeviceIdPref(context: Context, appWidgetId: Int) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||
prefs.remove(PREF_PREFIX_KEY + appWidgetId)
|
||||
prefs.apply()
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||
remove(PREF_PREFIX_KEY + appWidgetId)
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import org.kde.kdeconnect.Device
|
||||
import org.kde.kdeconnect.KdeConnect
|
||||
import org.kde.kdeconnect_tp.BuildConfig
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import androidx.core.net.toUri
|
||||
|
||||
const val RUN_COMMAND_ACTION = "RUN_COMMAND_ACTION"
|
||||
const val TARGET_COMMAND = "TARGET_COMMAND"
|
||||
@@ -65,10 +66,10 @@ class RunCommandWidgetProvider : AppWidgetProvider() {
|
||||
Log.e("RunCommandWidget", "Error running command", ex)
|
||||
}
|
||||
} else {
|
||||
Log.w("RunCommandWidget", "Device not available or runcommand plugin disabled");
|
||||
Log.w("RunCommandWidget", "Device not available or runcommand plugin disabled")
|
||||
}
|
||||
} else {
|
||||
super.onReceive(context, intent);
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +115,7 @@ internal fun updateAppWidget(
|
||||
val views = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_remotecommandplugin)
|
||||
assignTitleIntent(context, appWidgetId, views)
|
||||
|
||||
Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name)
|
||||
Log.d("WidgetProvider", "updateAppWidget device: " + (device?.name ?: "null"))
|
||||
|
||||
// Android should automatically toggle between the command list and the error text
|
||||
views.setEmptyView(R.id.widget_command_list, R.id.widget_error_text)
|
||||
@@ -174,7 +175,7 @@ private fun assignTitleIntent(context: Context, appWidgetId: Int, views: RemoteV
|
||||
private fun assignListAdapter(context: Context, appWidgetId: Int, views: RemoteViews) {
|
||||
val dataProviderIntent = Intent(context, CommandsRemoteViewsService::class.java)
|
||||
dataProviderIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
dataProviderIntent.data = Uri.parse(dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
dataProviderIntent.data = dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME).toUri()
|
||||
views.setRemoteAdapter(R.id.widget_command_list, dataProviderIntent)
|
||||
}
|
||||
|
||||
|
@@ -181,7 +181,7 @@ class SMSPlugin : Plugin() {
|
||||
@Deprecated("")
|
||||
private fun smsBroadcastReceivedDeprecated(messages: MutableList<SmsMessage>) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (messages.size <= 0) {
|
||||
if (messages.isEmpty()) {
|
||||
throw AssertionError("This method requires at least one message")
|
||||
}
|
||||
}
|
||||
@@ -224,11 +224,11 @@ class SMSPlugin : Plugin() {
|
||||
get() = R.string.telepathy_permission_explanation
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
val filter: IntentFilter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
|
||||
val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
|
||||
filter.priority = 500
|
||||
context.registerReceiver(receiver, filter)
|
||||
|
||||
val refreshFilter: IntentFilter = IntentFilter(Transaction.REFRESH)
|
||||
val refreshFilter = IntentFilter(Transaction.REFRESH)
|
||||
refreshFilter.priority = 500
|
||||
context.registerReceiver(messagesUpdateReceiver, refreshFilter, ContextCompat.RECEIVER_EXPORTED)
|
||||
|
||||
@@ -269,13 +269,11 @@ class SMSPlugin : Plugin() {
|
||||
val subID = np.getLong("subID", -1)
|
||||
|
||||
val jsonAddressList = np.getJSONArray("addresses")
|
||||
val addressList: List<SMSHelper.Address>
|
||||
if (jsonAddressList == null) {
|
||||
val addressList = if (jsonAddressList == null) {
|
||||
// If jsonAddressList is null, then the SMS_REQUEST packet is most probably from the older version of the desktop app.
|
||||
addressList = listOf(SMSHelper.Address(context, np.getString("phoneNumber")))
|
||||
}
|
||||
else {
|
||||
addressList = jsonArrayToAddressList(context, jsonAddressList)
|
||||
listOf(SMSHelper.Address(context, np.getString("phoneNumber")))
|
||||
} else {
|
||||
jsonArrayToAddressList(context, jsonAddressList)
|
||||
}
|
||||
val attachedFiles: List<SMSHelper.Attachment> = jsonArrayToAttachmentsList(np.getJSONArray("attachments"))
|
||||
|
||||
@@ -350,11 +348,10 @@ class SMSPlugin : Plugin() {
|
||||
numberToGet = null
|
||||
}
|
||||
|
||||
val conversation: List<SMSHelper.Message>
|
||||
if (rangeStartTimestamp < 0) {
|
||||
conversation = getMessagesInThread(this.context, threadID, numberToGet)
|
||||
val conversation = if (rangeStartTimestamp < 0) {
|
||||
getMessagesInThread(this.context, threadID, numberToGet)
|
||||
} else {
|
||||
conversation = getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet, true)
|
||||
getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet, true)
|
||||
}
|
||||
|
||||
val reply: NetworkPacket = constructBulkMessagePacket(conversation)
|
||||
|
@@ -11,10 +11,10 @@ import android.content.ContentResolver
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.Settings
|
||||
import androidx.core.net.toUri
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.kde.kdeconnect.Helpers.NetworkHelper.localIpAddress
|
||||
@@ -44,7 +44,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
return if (SimpleSftpServer.SUPPORTS_NATIVEFS) {
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
SftpSettingsFragment.getStorageInfoList(context, this).size != 0
|
||||
SftpSettingsFragment.getStorageInfoList(context, this).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
} else {
|
||||
val storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this)
|
||||
storageInfoList.sortBy { it.uri }
|
||||
if (storageInfoList.size <= 0) {
|
||||
if (storageInfoList.isEmpty()) {
|
||||
device.sendPacket(NetworkPacket(PACKET_TYPE_SFTP).apply {
|
||||
this["errorMessage"] = context.getString(R.string.sftp_no_storage_locations_configured)
|
||||
})
|
||||
@@ -127,7 +127,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
this["password"] = server.regeneratePassword()
|
||||
// Kept for compatibility, in case "multiPaths" is not possible or the other end does not support it
|
||||
this["path"] = if (paths.size == 1) paths[0] else "/"
|
||||
if (paths.size > 0) {
|
||||
if (paths.isNotEmpty()) {
|
||||
this["multiPaths"] = paths
|
||||
this["pathNames"] = pathNames
|
||||
}
|
||||
@@ -240,7 +240,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
@Throws(JSONException::class)
|
||||
fun fromJSON(jsonObject: JSONObject): StorageInfo { // TODO: Use Result after migrate callee to Kotlin
|
||||
val displayName = jsonObject.getString(KEY_DISPLAY_NAME)
|
||||
val uri = Uri.parse(jsonObject.getString(KEY_URI))
|
||||
val uri = jsonObject.getString(KEY_URI).toUri()
|
||||
|
||||
return StorageInfo(displayName, uri)
|
||||
}
|
||||
|
@@ -48,6 +48,7 @@ import java.security.KeyPair
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.PublicKey
|
||||
import androidx.core.net.toUri
|
||||
|
||||
internal class SimpleSftpServer {
|
||||
private lateinit var sshd: SshServer
|
||||
@@ -110,7 +111,7 @@ internal class SimpleSftpServer {
|
||||
withFileSystemAccessor(object : SftpFileSystemAccessor {
|
||||
fun notifyMediaStore(path: Path) {
|
||||
kotlin.runCatching {
|
||||
val uri = Uri.parse(path.toUri().toString())
|
||||
val uri = path.toUri().toString().toUri()
|
||||
MediaStoreHelper.indexFile(context, uri)
|
||||
uri
|
||||
}.fold(
|
||||
|
@@ -5,14 +5,12 @@
|
||||
*/
|
||||
package org.kde.kdeconnect.Plugins.SftpPlugin.saf
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Method
|
||||
|
@@ -17,7 +17,6 @@ import android.webkit.URLUtil;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
@@ -125,7 +124,7 @@ public class ShareActivity extends BaseActivity<ActivityShareBinding> {
|
||||
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(device.getDeviceId(), SharePlugin.class);
|
||||
if (intentHasUrl && !device.isReachable()) {
|
||||
// Store the URL to be delivered once device becomes online
|
||||
storeUrlForFutureDelivery(device, intent.toUri(0));
|
||||
storeUrlForFutureDelivery(device, intent.getStringExtra(Intent.EXTRA_TEXT));
|
||||
} else if (plugin != null) {
|
||||
plugin.share(intent);
|
||||
}
|
||||
@@ -188,6 +187,15 @@ public class ShareActivity extends BaseActivity<ActivityShareBinding> {
|
||||
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class);
|
||||
if (plugin != null) {
|
||||
plugin.share(intent);
|
||||
} else {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null && extras.containsKey(Intent.EXTRA_TEXT)) {
|
||||
final Device device = KdeConnect.getInstance().getDevice(deviceId);
|
||||
if (doesIntentContainUrl(intent) && device != null && !device.isReachable()) {
|
||||
final String text = extras.getString(Intent.EXTRA_TEXT);
|
||||
storeUrlForFutureDelivery(device, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
|
@@ -22,6 +22,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.LocusIdCompat;
|
||||
@@ -90,23 +91,7 @@ public class SharePlugin extends Plugin {
|
||||
public boolean onCreate() {
|
||||
super.onCreate();
|
||||
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
Intent shortcutIntent = new Intent(context, MainActivity.class);
|
||||
shortcutIntent.setAction(Intent.ACTION_VIEW);
|
||||
shortcutIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, device.getDeviceId());
|
||||
|
||||
IconCompat icon = IconCompat.createWithResource(context, device.getDeviceType().toShortcutDrawableId());
|
||||
|
||||
ShortcutInfoCompat shortcut = new ShortcutInfoCompat
|
||||
.Builder(context, device.getDeviceId())
|
||||
.setIntent(shortcutIntent)
|
||||
.setIcon(icon)
|
||||
.setShortLabel(device.getName())
|
||||
.setCategories(Set.of("org.kde.kdeconnect.category.SHARE_TARGET"))
|
||||
.setLocusId(new LocusIdCompat(device.getDeviceId()))
|
||||
.build();
|
||||
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
|
||||
|
||||
createOrUpdateDynamicShortcut(null);
|
||||
// Deliver URLs previously shared to this device now that it's connected
|
||||
deliverPreviouslySentIntents();
|
||||
return true;
|
||||
@@ -114,10 +99,49 @@ public class SharePlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
ShortcutManagerCompat.removeLongLivedShortcuts(context, List.of(device.getDeviceId()));
|
||||
for (ShortcutInfoCompat shortcut : ShortcutManagerCompat.getDynamicShortcuts(context)) {
|
||||
if (!shortcut.getId().equals(device.getDeviceId())) continue;
|
||||
if (!device.isReachable() && shortcut.isPinned()) {
|
||||
// Create an updated shortcut with the same ID
|
||||
createOrUpdateDynamicShortcut(shortcut);
|
||||
break;
|
||||
} else {
|
||||
ShortcutManagerCompat.removeLongLivedShortcuts(context, List.of(shortcut.getId()));
|
||||
}
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void createOrUpdateDynamicShortcut(@Nullable ShortcutInfoCompat shortcutToUpdate) {
|
||||
final boolean isNewShortcut = shortcutToUpdate == null;
|
||||
IconCompat icon = IconCompat.createWithResource(
|
||||
context, device.getDeviceType().toShortcutDrawableId());
|
||||
Intent shortcutIntent = null;
|
||||
if (isNewShortcut) {
|
||||
shortcutIntent = new Intent(context, MainActivity.class);
|
||||
shortcutIntent.setAction(Intent.ACTION_VIEW);
|
||||
shortcutIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, device.getDeviceId());
|
||||
}
|
||||
ShortcutInfoCompat shortcut = new ShortcutInfoCompat
|
||||
.Builder(context, device.getDeviceId())
|
||||
.setIntent(isNewShortcut ? shortcutIntent : shortcutToUpdate.getIntent())
|
||||
.setIcon(icon)
|
||||
.setShortLabel(isNewShortcut ? device.getName()
|
||||
: context.getString(
|
||||
R.string.unreachable_device_dynamic_shortcut,
|
||||
shortcutToUpdate.getShortLabel()))
|
||||
.setCategories(isNewShortcut ? Set.of("org.kde.kdeconnect.category.SHARE_TARGET")
|
||||
: shortcutToUpdate.getCategories())
|
||||
.setLocusId(isNewShortcut ? new LocusIdCompat(device.getDeviceId())
|
||||
: shortcutToUpdate.getLocusId())
|
||||
.build();
|
||||
if (isNewShortcut) {
|
||||
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
|
||||
} else {
|
||||
ShortcutManagerCompat.updateShortcuts(context, List.of(shortcut));
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverPreviouslySentIntents() {
|
||||
Set<String> currentUrlSet = mSharedPrefs.getStringSet(KEY_UNREACHABLE_URL_LIST + device.getDeviceId(), null);
|
||||
if (currentUrlSet != null) {
|
||||
@@ -125,6 +149,7 @@ public class SharePlugin extends Plugin {
|
||||
Intent intent;
|
||||
try {
|
||||
intent = Intent.parseUri(url, 0);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
} catch (URISyntaxException ex) {
|
||||
Log.e("SharePlugin", "Malformed URI");
|
||||
continue;
|
||||
@@ -316,12 +341,8 @@ public class SharePlugin extends Plugin {
|
||||
isUrl = false;
|
||||
}
|
||||
NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST);
|
||||
if (isUrl) {
|
||||
np.set("url", text);
|
||||
} else {
|
||||
np.set("text", text);
|
||||
}
|
||||
getDevice().sendPacket(np);
|
||||
np.set(isUrl ? "url" : "text", text);
|
||||
device.sendPacket(np);
|
||||
} else {
|
||||
Log.e("SharePlugin", "There's nothing we know how to share");
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
|
@@ -34,7 +34,6 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
@@ -21,6 +21,7 @@ import org.kde.kdeconnect.UserInterface.MainActivity
|
||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class AboutFragment : Fragment() {
|
||||
private var _binding: FragmentAboutBinding? = null
|
||||
@@ -113,7 +114,7 @@ class AboutFragment : Fragment() {
|
||||
button.visibility = View.GONE
|
||||
} else {
|
||||
button.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
startActivity(Intent(Intent.ACTION_VIEW, url.toUri()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import androidx.appcompat.widget.TooltipCompat
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.AboutPersonListItemEntryBinding
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class AboutPersonEntryItem(val person: AboutPerson) : ListAdapter.Item {
|
||||
override fun inflateView(layoutInflater: LayoutInflater): View {
|
||||
@@ -31,7 +32,8 @@ class AboutPersonEntryItem(val person: AboutPerson) : ListAdapter.Item {
|
||||
binding.aboutPersonListItemEntryVisitHomepageButton.visibility = View.VISIBLE
|
||||
TooltipCompat.setTooltipText(binding.aboutPersonListItemEntryVisitHomepageButton, layoutInflater.context.resources.getString(R.string.visit_contributors_homepage, person.webAddress))
|
||||
binding.aboutPersonListItemEntryVisitHomepageButton.setOnClickListener {
|
||||
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(person.webAddress)))
|
||||
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW,
|
||||
person.webAddress.toUri()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -194,7 +194,7 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
saveList();
|
||||
showEmptyListMessageIfRequired();
|
||||
})
|
||||
.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
.addCallback(new BaseTransientBottomBar.BaseCallback<>() {
|
||||
@Override
|
||||
public void onDismissed(Snackbar transientBottomBar, int event) {
|
||||
switch (event) {
|
||||
@@ -250,11 +250,17 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
showEmptyListMessageIfRequired();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_duplicate, Toast.LENGTH_SHORT).show();
|
||||
Context context = addDeviceDialog.getContext();
|
||||
if (context != null) {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_duplicate, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_invalid, Toast.LENGTH_SHORT).show();
|
||||
Context context = addDeviceDialog.getContext();
|
||||
if (context != null) {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_invalid, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,8 +272,8 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
}
|
||||
|
||||
private static class DeletedCustomDevice {
|
||||
@NonNull DeviceHost hostnameOrIP;
|
||||
int position;
|
||||
@NonNull final DeviceHost hostnameOrIP;
|
||||
final int position;
|
||||
|
||||
DeletedCustomDevice(@NonNull DeviceHost hostnameOrIP, int position) {
|
||||
this.hostnameOrIP = hostnameOrIP;
|
||||
|
@@ -39,7 +39,9 @@ import org.kde.kdeconnect.UserInterface.About.AboutFragment
|
||||
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
|
||||
import java.util.LinkedList
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.size
|
||||
import androidx.core.view.get
|
||||
|
||||
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
|
||||
private const val MENU_ENTRY_SETTINGS = 2
|
||||
@@ -59,7 +61,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
private var mCurrentDevice: String? = null
|
||||
private var mCurrentMenuEntry = 0
|
||||
private set(value) {
|
||||
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
|
||||
@@ -87,7 +89,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
val root = binding.root
|
||||
setContentView(root)
|
||||
mDrawerLayout = if (root is DrawerLayout) root else null
|
||||
mDrawerLayout = root as? DrawerLayout
|
||||
|
||||
val mDrawerHeader = mNavigationView.getHeaderView(0)
|
||||
mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name)
|
||||
@@ -115,17 +117,17 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
when (mCurrentMenuEntry) {
|
||||
MENU_ENTRY_ADD_DEVICE -> {
|
||||
mCurrentDevice = null
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(PairingFragment())
|
||||
}
|
||||
|
||||
MENU_ENTRY_SETTINGS -> {
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(SettingsFragment())
|
||||
}
|
||||
|
||||
MENU_ENTRY_ABOUT -> {
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(AboutFragment.newInstance(getApplicationAboutData(this)))
|
||||
}
|
||||
|
||||
@@ -207,7 +209,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
if(missingPermissions.size > 0){
|
||||
if(missingPermissions.isNotEmpty()){
|
||||
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), RESULT_NOTIFICATIONS_ENABLED)
|
||||
}
|
||||
}
|
||||
@@ -303,7 +305,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
@JvmOverloads
|
||||
fun onDeviceSelected(deviceId: String?, fromDeviceList: Boolean = false) {
|
||||
mCurrentDevice = deviceId
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, deviceId) }
|
||||
if (mCurrentDevice != null) {
|
||||
mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId)
|
||||
if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) {
|
||||
@@ -364,9 +366,9 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
if (isPermissionGranted(permissions, grantResults, Manifest.permission.BLUETOOTH_CONNECT) &&
|
||||
isPermissionGranted(permissions, grantResults, Manifest.permission.BLUETOOTH_SCAN)) {
|
||||
val preferenceEditor = PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||
preferenceEditor.putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, true)
|
||||
preferenceEditor.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit {
|
||||
putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, true)
|
||||
}
|
||||
setContentFragment(SettingsFragment())
|
||||
}
|
||||
|
||||
@@ -390,9 +392,9 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
|
||||
private fun uncheckAllMenuItems(menu: Menu) {
|
||||
val size = menu.size()
|
||||
val size = menu.size
|
||||
for (i in 0 until size) {
|
||||
val item = menu.getItem(i)
|
||||
val item = menu[i]
|
||||
item.subMenu?.let { uncheckAllMenuItems(it) } ?: item.setChecked(false)
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
import org.kde.kdeconnect_tp.databinding.DevicesListBinding;
|
||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationDuplicateNamesBinding;
|
||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationNotTrustedBinding;
|
||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextBinding;
|
||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoNotificationsBinding;
|
||||
@@ -46,6 +47,7 @@ import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoWifiBinding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
|
||||
/**
|
||||
@@ -60,6 +62,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
private PairingExplanationNotTrustedBinding pairingExplanationNotTrustedBinding;
|
||||
private PairingExplanationTextBinding pairingExplanationTextBinding;
|
||||
private PairingExplanationTextNoWifiBinding pairingExplanationTextNoWifiBinding;
|
||||
private PairingExplanationDuplicateNamesBinding pairingExplanationDuplicateNamesBinding;
|
||||
private PairingExplanationTextNoNotificationsBinding pairingExplanationTextNoNotificationsBinding;
|
||||
|
||||
private MainActivity mActivity;
|
||||
@@ -68,6 +71,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
|
||||
private TextView headerText;
|
||||
private TextView noWifiHeader;
|
||||
private TextView duplicateNamesHeader;
|
||||
private TextView noNotificationsHeader;
|
||||
private TextView notTrustedText;
|
||||
|
||||
@@ -94,6 +98,9 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
noWifiHeader = pairingExplanationTextNoWifiBinding.getRoot();
|
||||
noWifiHeader.setOnClickListener(view -> startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
|
||||
|
||||
pairingExplanationDuplicateNamesBinding = PairingExplanationDuplicateNamesBinding.inflate(inflater);
|
||||
duplicateNamesHeader = pairingExplanationDuplicateNamesBinding.getRoot();
|
||||
|
||||
pairingExplanationTextNoNotificationsBinding = PairingExplanationTextNoNotificationsBinding.inflate(inflater);
|
||||
noNotificationsHeader = pairingExplanationTextNoNotificationsBinding.getRoot();
|
||||
noNotificationsHeader.setOnClickListener(view -> ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.POST_NOTIFICATIONS}, MainActivity.RESULT_NOTIFICATIONS_ENABLED));
|
||||
@@ -162,6 +169,8 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
}
|
||||
listRefreshCalledThisFrame = true;
|
||||
|
||||
devicesListBinding.devicesList.removeHeaderView(duplicateNamesHeader);
|
||||
|
||||
//Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special
|
||||
BackgroundService service = BackgroundService.getInstance();
|
||||
if (service == null) {
|
||||
@@ -176,10 +185,20 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
SectionItem connectedSection;
|
||||
Resources res = getResources();
|
||||
|
||||
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
|
||||
|
||||
HashSet<String> seenNames = new HashSet<>();
|
||||
for (Device device : devices) {
|
||||
if (seenNames.contains(device.getName())) {
|
||||
devicesListBinding.devicesList.addHeaderView(duplicateNamesHeader);
|
||||
break;
|
||||
}
|
||||
seenNames.add(device.getName());
|
||||
}
|
||||
|
||||
connectedSection = new SectionItem(res.getString(R.string.category_connected_devices));
|
||||
items.add(connectedSection);
|
||||
|
||||
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
|
@@ -6,12 +6,12 @@
|
||||
package org.kde.kdeconnect.UserInterface
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputFilter
|
||||
@@ -21,6 +21,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
@@ -28,14 +29,22 @@ import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.kde.kdeconnect.BackgroundService
|
||||
import org.kde.kdeconnect.Helpers.CreateFileParams
|
||||
import org.kde.kdeconnect.Helpers.CreateFileResultContract
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper.filterName
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper.getDeviceName
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil.applyTheme
|
||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||
import org.kde.kdeconnect_tp.BuildConfig
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
@@ -55,8 +64,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
persistentNotificationPref(context),
|
||||
trustedNetworkPref(context),
|
||||
devicesByIpPref(context),
|
||||
udpBroadcastPref(context),
|
||||
bluetoothSupportPref(context),
|
||||
exportLogsPref(context),
|
||||
moreSettingsPref(context),
|
||||
).forEach(screen::addPreference)
|
||||
|
||||
@@ -190,12 +199,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
))
|
||||
}
|
||||
|
||||
private fun udpBroadcastPref(context: Context) = SwitchPreference(context).apply {
|
||||
setDefaultValue(true)
|
||||
key = KEY_UDP_BROADCAST_ENABLED
|
||||
setTitle(R.string.enable_udp_broadcast)
|
||||
}
|
||||
|
||||
private fun bluetoothSupportPref(context: Context) = SwitchPreference(context).apply {
|
||||
setDefaultValue(false)
|
||||
key = KEY_BLUETOOTH_ENABLED
|
||||
@@ -218,6 +221,31 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportLogsPref(context: Context) = Preference(context).apply {
|
||||
isPersistent = false
|
||||
setTitle(R.string.settings_export_logs)
|
||||
setSummary(R.string.settings_export_logs_text)
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
exportLogs.launch(CreateFileParams("text/plain", "kdeconnect-log.txt"))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private val exportLogs: ActivityResultLauncher<CreateFileParams> = registerForActivityResult(
|
||||
CreateFileResultContract()
|
||||
) { uri: Uri? ->
|
||||
val output = uri?.let { context?.contentResolver?.openOutputStream(uri) } ?: return@registerForActivityResult
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d"))
|
||||
val reader = InputStreamReader(process.inputStream)
|
||||
output.use {
|
||||
it.write("KDE Connect ${BuildConfig.VERSION_NAME}\n".toByteArray(Charsets.UTF_8))
|
||||
it.write("Android ${Build.VERSION.RELEASE} (${Build.MANUFACTURER} ${Build.MODEL})\n".toByteArray(Charsets.UTF_8))
|
||||
IOUtils.copy(reader, it, Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun moreSettingsPref(context: Context) = Preference(context).apply {
|
||||
isPersistent = false
|
||||
isSelectable = false
|
||||
@@ -226,7 +254,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_UDP_BROADCAST_ENABLED: String = "udp_broadcast_enabled"
|
||||
const val KEY_BLUETOOTH_ENABLED: String = "bluetooth_enabled"
|
||||
const val KEY_APP_THEME: String = "theme_pref"
|
||||
}
|
||||
|
@@ -6,9 +6,8 @@
|
||||
package org.kde.kdeconnect.UserInterface
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class StartActivityAlertDialogFragment : AlertDialogFragment() {
|
||||
private var intentAction: String? = null
|
||||
@@ -36,8 +35,9 @@ class StartActivityAlertDialogFragment : AlertDialogFragment() {
|
||||
|
||||
setCallback(object : Callback() {
|
||||
override fun onPositiveButtonClicked() {
|
||||
val intentUrl = intentUrl
|
||||
val intent = if (!intentUrl.isNullOrEmpty()) {
|
||||
Intent(intentAction, Uri.parse(intentUrl))
|
||||
Intent(intentAction, intentUrl.toUri())
|
||||
} else {
|
||||
Intent(intentAction)
|
||||
}
|
||||
|
@@ -9,8 +9,11 @@ package org.kde.kdeconnect.UserInterface.compose
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@@ -6,7 +6,6 @@
|
||||
package org.kde.kdeconnect.async
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
abstract class BackgroundJob<I, R> : Runnable {
|
||||
private val callback: Callback<R>
|
||||
|
@@ -129,7 +129,7 @@ class DeviceTest {
|
||||
val deviceId = "testDevice"
|
||||
val settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE)
|
||||
val deviceInfo = loadFromSettings(context, deviceId, settings)
|
||||
deviceInfo.protocolVersion = DeviceHelper.ProtocolVersion
|
||||
deviceInfo.protocolVersion = DeviceHelper.PROTOCOL_VERSION
|
||||
deviceInfo.incomingCapabilities = hashSetOf("kdeconnect.plugin1State", "kdeconnect.plugin2State")
|
||||
deviceInfo.outgoingCapabilities = hashSetOf("kdeconnect.plugin1State.request", "kdeconnect.plugin2State.request")
|
||||
|
||||
@@ -208,7 +208,7 @@ class DeviceTest {
|
||||
val deviceId = "unpairedTestDevice"
|
||||
fakeNetworkPacket["deviceId"] = deviceId
|
||||
fakeNetworkPacket["deviceName"] = "Unpaired Test Device"
|
||||
fakeNetworkPacket["protocolVersion"] = DeviceHelper.ProtocolVersion
|
||||
fakeNetworkPacket["protocolVersion"] = DeviceHelper.PROTOCOL_VERSION
|
||||
fakeNetworkPacket["deviceType"] = DeviceType.PHONE.toString()
|
||||
val certificateString =
|
||||
"""
|
||||
|
@@ -12,7 +12,6 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||
import org.kde.kdeconnect.MockSharedPreference
|
||||
import org.kde.kdeconnect.PairingHandler
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.MockedStatic
|
||||
import org.mockito.Mockito
|
||||
|
Reference in New Issue
Block a user