2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-05 00:25:09 +00:00

Compare commits

..

40 Commits

Author SHA1 Message Date
Albert Vaca Cintora
ed1dcdab52 Release 1.33.4 2025-05-02 17:24:53 +02:00
Albert Vaca Cintora
e3ccfb1b25 Bump deps 2025-05-02 17:12:15 +02:00
Albert Vaca Cintora
4ae04ae060 Refactor shared code out, apply ratelimit also to TCP 2025-05-02 17:05:55 +02:00
Albert Vaca Cintora
801367458e Improve rate-limit to also limit by IP 2025-05-02 17:05:55 +02:00
Albert Vaca Cintora
26c72fae89 Do not filter logs by PID
So we can see crashes in previous sessions of the app. Also, Android only
lets apps see their own logs anyway (at least in recent versions), so there
should be no privacy concerns from someone sharing those logs.
2025-05-02 17:02:40 +02:00
l10n daemon script
b981f9234b GIT_SILENT made messages (after extraction) 2025-04-29 01:55:52 +00:00
l10n daemon script
7f07e4658f GIT_SILENT made messages (after extraction) 2025-04-28 01:58:08 +00:00
l10n daemon script
f182d27ebe GIT_SILENT made messages (after extraction) 2025-04-27 01:57:46 +00:00
l10n daemon script
3b9d6ac61e GIT_SILENT made messages (after extraction) 2025-04-22 01:56:54 +00:00
l10n daemon script
6f24ac8d25 GIT_SILENT made messages (after extraction) 2025-04-20 01:54:52 +00:00
l10n daemon script
2891ec2092 GIT_SILENT made messages (after extraction) 2025-04-19 01:56:31 +00:00
Albert Vaca Cintora
fc1424c67c Better pairing fragment layout 2025-04-18 19:51:51 +02:00
Albert Vaca Cintora
83efd6b355 Add a warning if multiple devices have the same name 2025-04-18 17:42:08 +00:00
Albert Vaca Cintora
05e14bb81c Remove setting to disable UDP broadcast 2025-04-18 10:30:22 +02:00
l10n daemon script
1236cbe4e3 GIT_SILENT made messages (after extraction) 2025-04-17 01:54:46 +00:00
l10n daemon script
5f9159a13f GIT_SILENT made messages (after extraction) 2025-04-16 01:55:03 +00:00
l10n daemon script
fbd9f8f216 GIT_SILENT made messages (after extraction) 2025-04-15 01:56:01 +00:00
l10n daemon script
e32b6b67e0 GIT_SILENT made messages (after extraction) 2025-04-14 01:55:17 +00:00
Vala Zadeh
63a849b80a Extend offline URL sharing behavior
## Summary

Previously, URLs shared to offline targets via the ShareActivity were
delivered to them once they became online. This change extends this
behavior to direct share targets.

## Test Plan

Test 1

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC unreachable, e.g., close the KDE app on it.
* On the phone, open Chrome and share a couple of webpages to KDE's PC's direct share target.
* Open the KDE app on the PC.
* Observe that the webpages are opened on PC.

Test 2

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC unreachable, e.g., close the KDE app on it.
* On the phone, share a file to KDE's PC's direct share target.
* Open the KDE app on the PC.
* Observe that the file is sent to PC.

Test 3

* Make sure two PC devices are already paired with the phone. Let's say the name of these devices are "PC1" and "PC2".
* Make PC1 and PC2 unreachable, e.g., close the KDE app on them.
* On the phone, open Chrome and share a couple of webpages to KDE's PC1's direct share target.
* Open the KDE app on the PC2.
* Observe that the webpages are NOT opened on PC2.
* Open the KDE app on the PC1.
* Observe that the webpages are opened on PC1.

Test 4

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC reachable, e.g., have the KDE app open on it.
* Try to share a URL from the phone to the KDE's PC's direct share target.
* Observe that the URL opens instantly on the PC.
2025-04-13 00:20:04 +00:00
l10n daemon script
170bb5e717 GIT_SILENT made messages (after extraction) 2025-04-12 01:51:20 +00:00
l10n daemon script
f121e4982e GIT_SILENT made messages (after extraction) 2025-04-10 01:54:33 +00:00
l10n daemon script
937289730d GIT_SILENT made messages (after extraction) 2025-04-09 01:54:22 +00:00
l10n daemon script
624a9302fd GIT_SILENT made messages (after extraction) 2025-04-08 02:00:37 +00:00
l10n daemon script
e6f4b69464 GIT_SILENT made messages (after extraction) 2025-04-07 02:06:50 +00:00
Albert Vaca Cintora
2190c9cdaa Missed updated changelog 2025-04-06 10:32:38 +02:00
Albert Vaca Cintora
864d44cb5b Release 1.33.3 2025-04-06 10:30:22 +02:00
Albert Vaca Cintora
72e958a891 Allow exporting the logs form the app 2025-04-06 10:24:28 +02:00
Albert Vaca Cintora
d4ab2ca6cf Bump deps 2025-04-06 10:24:14 +02:00
Albert Vaca Cintora
fd51ec7c14 Fix linter warnings 2025-04-05 00:44:08 +02:00
Albert Vaca Cintora
28070954a6 Regenerate device ID if the stored ID is not valid 2025-04-05 00:05:56 +02:00
Albert Vaca Cintora
e10f2496de Simplify running git in gradle 2025-04-04 14:06:20 +02:00
Albert Vaca Cintora
95b4c08605 Bump deps 2025-04-04 12:15:11 +02:00
l10n daemon script
51d4de34c4 GIT_SILENT made messages (after extraction) 2025-04-04 02:01:41 +00:00
l10n daemon script
de2001bbe1 GIT_SILENT made messages (after extraction) 2025-03-31 01:55:34 +00:00
Albert Vaca Cintora
9c80cb9a40 Remove old code that used Android IDs as device IDs 2025-03-30 21:01:32 +02:00
Albert Vaca Cintora
0b03a66c37 Generate IDs with only alphanumeric values
As per https://invent.kde.org/network/kdeconnect-meta/-/merge_requests/13
2025-03-30 20:59:38 +02:00
l10n daemon script
6d66d69820 GIT_SILENT made messages (after extraction) 2025-03-27 01:58:41 +00:00
Albert Vaca Cintora
c0fc19baaa Bump version of classindexksp 2025-03-18 12:56:45 +01:00
Albert Vaca Cintora
03ea5eae4c Fix NPE 2025-03-17 14:03:15 +01:00
l10n daemon script
b373c28cdd GIT_SILENT made messages (after extraction) 2025-03-16 01:58:31 +00:00
81 changed files with 596 additions and 337 deletions

View File

@@ -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) {

View 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.

View 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).

View File

@@ -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]

View 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>

View 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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 URLler, 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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() {

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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.");
}

View 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
}
}

View File

@@ -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)
}

View File

@@ -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 ->

View File

@@ -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(

View File

@@ -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;

View File

@@ -6,7 +6,6 @@
package org.kde.kdeconnect.Helpers
import android.net.Uri
import android.provider.DocumentsContract
object StorageHelper {
fun getDisplayName(treeUri: Uri): String {

View File

@@ -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

View File

@@ -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) }
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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();
}
}
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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()
}
}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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")) {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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 {

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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()))
}
}
}

View File

@@ -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()))
}
}

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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));

View File

@@ -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"
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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 =
"""

View File

@@ -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