mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-31 22:25:08 +00:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2190c9cdaa | ||
|
864d44cb5b | ||
|
72e958a891 | ||
|
d4ab2ca6cf | ||
|
fd51ec7c14 | ||
|
28070954a6 | ||
|
e10f2496de | ||
|
95b4c08605 | ||
|
51d4de34c4 | ||
|
de2001bbe1 | ||
|
9c80cb9a40 | ||
|
0b03a66c37 | ||
|
6d66d69820 | ||
|
c0fc19baaa | ||
|
03ea5eae4c | ||
|
b373c28cdd | ||
|
6c8d22b1ed | ||
|
69adfbfbc2 | ||
|
f80e29538a | ||
|
56dda889d1 |
@@ -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 = 13301
|
||||
versionName = "1.33.1"
|
||||
versionCode = 13303
|
||||
versionName = "1.33.3"
|
||||
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) {
|
||||
|
12
fastlane/metadata/android/en-US/changelogs/13302.txt
Normal file
12
fastlane/metadata/android/en-US/changelogs/13302.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
1.33.2
|
||||
* Fix connection issues on some devices
|
||||
|
||||
1.33.1
|
||||
* Fix compatibility with GSConnect
|
||||
|
||||
1.33.0
|
||||
* Add support for PeerTube links
|
||||
* Allow filtering notifications from work profile
|
||||
* Fix bug where devices would unpair without user interaction
|
||||
* Verification key now changes every second (only if both devices support it)
|
||||
* Fix crashes
|
16
fastlane/metadata/android/en-US/changelogs/13303.txt
Normal file
16
fastlane/metadata/android/en-US/changelogs/13303.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
1.33.3
|
||||
* Fix more connection issues. Pairing again might be needed in some setups.
|
||||
* Add a setting to export the application logs.
|
||||
|
||||
1.33.2
|
||||
* Fix connection issues on some devices.
|
||||
|
||||
1.33.1
|
||||
* Fix compatibility with GSConnect.
|
||||
|
||||
1.33.0
|
||||
* Add support for PeerTube links.
|
||||
* Allow filtering notifications from work profile.
|
||||
* Fix bug where devices would unpair without user interaction.
|
||||
* Verification key now changes every second (only if both devices support it).
|
||||
* Fix crashes.
|
@@ -1,24 +1,24 @@
|
||||
[versions]
|
||||
activityCompose = "1.10.1"
|
||||
androidDesugarJdkLibs = "2.1.5"
|
||||
androidGradlePlugin = "8.9.0"
|
||||
androidGradlePlugin = "8.9.1"
|
||||
androidSmsmms = "kdeconnect-1-21-0"
|
||||
appcompat = "1.7.0"
|
||||
bcpkixJdk15on = "1.70"
|
||||
classindexksp = "1.1"
|
||||
classindexksp = "1.2"
|
||||
commonsCollections4 = "4.4"
|
||||
commonsIo = "2.18.0"
|
||||
commonsLang3 = "3.17.0"
|
||||
constraintlayoutCompose = "1.1.1"
|
||||
coreKtx = "1.15.0"
|
||||
dependencyLicenseReport = "2.7"
|
||||
dependencyLicenseReport = "2.9"
|
||||
disklrucache = "2.0.2"
|
||||
documentfile = "1.0.1"
|
||||
gridlayout = "1.0.0"
|
||||
jsonassert = "1.5.3"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.1.10"
|
||||
kspPlugin = "2.1.10-1.0.30"
|
||||
kotlin = "2.1.20"
|
||||
kspPlugin = "2.1.20-1.0.32"
|
||||
kotlinxCoroutinesCore = "1.10.1"
|
||||
lifecycleExtensions = "2.2.0"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
@@ -27,7 +27,7 @@ material = "1.12.0"
|
||||
material3 = "1.3.1"
|
||||
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"
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Kunhavigi la enhavon de la tondujo</string>
|
||||
<string name="pref_plugin_clipboard_sent">Clipboard Sendita</string>
|
||||
<string name="pref_plugin_mousepad">Fora enigo</string>
|
||||
<string name="pref_plugin_mousepad_desc">Uzu vian telefonon aŭ tablojdon kiel tuŝplaton kaj klavaron</string>
|
||||
<string name="pref_plugin_mousepad_desc">Uzu vian telefonon aŭ tabuleton kiel tuŝplaton kaj klavaron</string>
|
||||
<string name="pref_plugin_presenter">Prezento fora</string>
|
||||
<string name="pref_plugin_presenter_desc">Uzu vian aparaton por ŝanĝi lumbildojn en prezento</string>
|
||||
<string name="pref_plugin_remotekeyboard">Ricevi forajn klavojn</string>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string name="pref_plugin_mpris">Plurmedia regiloj</string>
|
||||
<string name="pref_plugin_mpris_desc">Provizas teleregilon por via plurmedia ludilo</string>
|
||||
<string name="pref_plugin_runcommand">Lanĉi Komandon</string>
|
||||
<string name="pref_plugin_runcommand_desc">Ekigi forajn komandojn de via telefono aŭ tablojdo</string>
|
||||
<string name="pref_plugin_runcommand_desc">Ekigi forajn komandojn de via telefono aŭ tabuleto</string>
|
||||
<string name="pref_plugin_contacts">Sinkronigilo de Kontaktoj</string>
|
||||
<string name="pref_plugin_contacts_desc">Permesi sinkronigi la kontaktlibron de la aparato</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Aparato ne atingebla</string>
|
||||
<string name="error_already_paired">Aparato jam parigita</string>
|
||||
<string name="error_timed_out">Tempo elĉerpita</string>
|
||||
<string name="error_clocks_not_match">Aparataj horloĝoj estas malsinkronaj</string>
|
||||
<string name="error_canceled_by_user">Nuligite de uzanto</string>
|
||||
<string name="error_canceled_by_other_peer">Nuligite de alia kunulo</string>
|
||||
<string name="encryption_info_title">Ĉifrada Informo</string>
|
||||
@@ -192,11 +193,13 @@
|
||||
<string name="share_to">Kunhavigi al…</string>
|
||||
<string name="unreachable_device">%s (Neatingebla)</string>
|
||||
<string name="unreachable_device_url_share_text">URL-oj kundividitaj al neatingebla aparato estos liveritaj al ĝi post kiam ĝi fariĝos atingebla.\n\n</string>
|
||||
<string name="protocol_version">Protokolversio:</string>
|
||||
<string name="protocol_version_newer">Ĉi tiu aparato uzas pli novan protokolversion</string>
|
||||
<string name="plugin_settings_with_name">%s agordoj</string>
|
||||
<string name="invalid_device_name">Nevalida aparato nomo</string>
|
||||
<string name="shareplugin_text_saved">Ricevita teksto, konservita en tondujo</string>
|
||||
<string name="custom_devices_settings">Propra aparato listo</string>
|
||||
<string name="custom_devices_settings_summary">%d aparatoj aldoniĝis permane</string>
|
||||
<string name="custom_device_list">Aldoni aparatojn per IP</string>
|
||||
<string name="custom_device_deleted">Propra aparato forigita</string>
|
||||
<string name="custom_device_list_help">Se via aparato ne estas aŭtomate detektita, vi povas aldoni ĝian IP-adreson aŭ gastigan nomon alklakante la Ŝveban Ago-Butonon</string>
|
||||
@@ -326,6 +329,7 @@
|
||||
<string name="empty_trusted_networks_list_text">Vi ankoraŭ ne aldonis neniun fidindan reton</string>
|
||||
<string name="allow_all_networks_text">Permesi ĉion</string>
|
||||
<string name="location_permission_needed_title">Permeso bezonata</string>
|
||||
<string name="bluetooth_permission_needed_desc">KDE Connect bezonas permeson por konekti al proksimaj aparatoj por fari aparatojn parigitaj per Bluetooth disponebla en KDE Connect.</string>
|
||||
<string name="location_permission_needed_desc">KDE Connect bezonas la fonlokan permeson por scii la WiFi-reton al kiu vi estas konektita eĉ kiam la programo estas en la fono. Ĉi tio estas ĉar la nomo de la WiFi-retoj ĉirkaŭ vi povus esti uzata por trovi vian lokon, eĉ kiam tion ne faras KDE Connect.</string>
|
||||
<string name="clipboard_android_x_incompat">Android 10 forigis aliron al tondujo al ĉiuj aplikaĵoj. Ĉi tiu kromaĵo estos malŝaltita.</string>
|
||||
<string name="mpris_open_url">Daŭre ludi ĉi tie</string>
|
||||
@@ -411,6 +415,7 @@
|
||||
<string name="tap_to_execute">Frapi por plenumi</string>
|
||||
<string name="plugin_stats">Statistiko de kromprogramoj</string>
|
||||
<string name="enable_udp_broadcast">Ebligi UDP-aparatan malkovron</string>
|
||||
<string name="enable_bluetooth">Ŝalti bluetooth (beta)</string>
|
||||
<string name="receive_notifications_permission_explanation">Sciigoj devas esti permesitaj ricevi ilin de aliaj aparatoj</string>
|
||||
<string name="findmyphone_notifications_explanation">La sciiga permeso estas necesa por ke la telefono povu sonori kiam la app estas en la fono</string>
|
||||
<string name="no_notifications">Sciigoj estas malŝaltitaj, vi ne ricevos alvenantajn parajn sciigojn.</string>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">Laite tavoittamattomissa</string>
|
||||
<string name="error_already_paired">Laite on jo kytketty pariksi</string>
|
||||
<string name="error_timed_out">Aikakatkaisu</string>
|
||||
<string name="error_clocks_not_match">Laitteiden kelloja ei ole tahdistettu</string>
|
||||
<string name="error_canceled_by_user">Käyttäjä perui</string>
|
||||
<string name="error_canceled_by_other_peer">Vertaiskäyttäjä perui</string>
|
||||
<string name="encryption_info_title">Salaustiedot</string>
|
||||
@@ -192,6 +193,7 @@
|
||||
<string name="share_to">Jaa…</string>
|
||||
<string name="unreachable_device">%s (tavoittamattomissa)</string>
|
||||
<string name="unreachable_device_url_share_text">Tavoittamattomissa olevalle laitteelle jaetut verkko-osoitteet välitetään heti kun laite tavoitetaan.\n\n</string>
|
||||
<string name="protocol_version">Yhteyskäytäntöversio:</string>
|
||||
<string name="protocol_version_newer">Laite käyttää uudempaa yhteyskäytäntöversiota</string>
|
||||
<string name="plugin_settings_with_name">%s-asetukset</string>
|
||||
<string name="invalid_device_name">Virheellinen laitenimi</string>
|
||||
|
@@ -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>
|
||||
|
@@ -399,7 +399,7 @@
|
||||
<string name="about_kde_report_bugs_or_wishes"><h1>Hataları veya İsteklerinizi Bildirin</h1> <p>Yazılımlar her zaman iyileştirilebilir ve KDE takımın bunu yapmaya hazırdır. Ancak siz de bir şey beklendiği gibi gitmezse veya hata verirse bize bildirin.</p> <p>KDE’nin bir hata takip sistemi vardır. <a href=https://bugs.kde.org/>https://bugs.kde.org/</a> adresini ziyaret edin veya hakkında ekranının ‘Hata Bildir’ düğmesini kullanarak hataları bildirin.</p> Bir iyileştirme için öneriniz varsa bunu bildirmek için hata takip sistemini kullanabilirsiniz; yalnızca “Wishlist” önceliğini kullandığınızdan emin olun.</string>
|
||||
<string name="about_kde_join_kde">"<h1>KDE’ye Katılın</h1> <p>KDE takımının bir üyesi olmak için yazılım geliştirici olmanıza gerek yoktur. Program arayüzlerini çeviren dil takımlarına katılabilirsiniz. Grafikler, temalar, sesler ve iyileştirilmiş belgelendirme sağlayabilirsiniz. Karar sizin!</p> <p>Katılabileceğiniz bazı projeler hakkında bilgi almak için <a href=https://community.kde.org/Get_Involved>https://community.kde.org/Get_Involved</a> sayfasını ziyaret edin.</p> Daha fazla bilgiye veya belgeye gereksiniminiz varsa <a href=https://techbase.kde.org/>https://techbase.kde.org/</a> sayfasında aradığınızı bulabilirsiniz."</string>
|
||||
<string name="about_kde_support_kde">"<h1>KDE’yi Destekleyin</h1> <p>KDE yazılımları her zaman ücretsiz kalmayı sürdürecektir; ancak bunu oluşturmak bedava değildir. </p> <p>Geliştirmeyi desteklemek için KDE topluluğu, kar amacı gütmeyen bir kuruluş olan KDE e.V.’yi kurmuştur, bu topluluk KDE topluğunu yasal ve finansal konularda temsil eder. KDE e.V. hakkında daha fazla bilgi için <a href=https://ev.kde.org/>https://ev.kde.org/</a> adresini ziyaret edin.</p> <p>KDE, finansal da dahil olmak üzere her türlü katkıdan yarar sağlar. Maddi kaynaklarımızla, geliştiricilerimizin ve diğerlerinin katkıda bulunurken oluşan masraflarını karşılıyoruz. Ayrıca yasal destek ve konferanslar ve toplantılar için de kullanılmaktadır.</p> <p>Emeklerimizi, finansal destekle desteklemeniz için <a href=https://www.kde.org/community/donations/>https://www.kde.org/community/donations/</a> adresinde bulunan yollardan birini kullanabilirsiniz.</p> Desteğiniz için şimdiden teşekkürler."</string>
|
||||
<string name="maintainer_and_developer">Projeyi sürdüren ve geliştirici</string>
|
||||
<string name="maintainer_and_developer">Bakımcı ve geliştirici</string>
|
||||
<string name="developer">Geliştirici</string>
|
||||
<string name="apple_support">macOS ve iOS desteği üzerinde çalışılmaktadır.</string>
|
||||
<string name="bug_fixes_and_general_improvements">Hata düzeltmeleri ve genel iyileştirmeler</string>
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<string name="error_not_reachable">裝置無法存取</string>
|
||||
<string name="error_already_paired">裝置已經配對</string>
|
||||
<string name="error_timed_out">逾時</string>
|
||||
<string name="error_clocks_not_match">裝置時鐘不同步</string>
|
||||
<string name="error_canceled_by_user">使用者中斷</string>
|
||||
<string name="error_canceled_by_other_peer">被其他同等功能應用中斷</string>
|
||||
<string name="encryption_info_title">加密資訊</string>
|
||||
@@ -184,6 +185,7 @@
|
||||
<string name="share_to">分享給…</string>
|
||||
<string name="unreachable_device">%s(無法存取)</string>
|
||||
<string name="unreachable_device_url_share_text">若分享網址 (URL) 到無法存取的裝置,將在該裝置變得可存取後再傳送過去。\n\n</string>
|
||||
<string name="protocol_version">協定版本:</string>
|
||||
<string name="protocol_version_newer">此裝置使用較新的通訊協定版本</string>
|
||||
<string name="plugin_settings_with_name">%s的設定</string>
|
||||
<string name="invalid_device_name">無效的裝置名稱</string>
|
||||
|
@@ -401,6 +401,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
|
||||
<string name="settings_rename">Device name</string>
|
||||
<string name="settings_dark_mode">Dark theme</string>
|
||||
<string name="settings_export_logs">Export KDE Connect logs</string>
|
||||
<string name="settings_export_logs_text">Generate a file with execution information that can help troubleshoot issues.</string>
|
||||
|
||||
<string name="settings_more_settings_title">More settings</string>
|
||||
<string name="settings_more_settings_text">Per-device settings can be found under \'Plugin settings\' from within a device.</string>
|
||||
<string name="setting_persistent_notification">Show persistent notification</string>
|
||||
|
@@ -24,13 +24,16 @@ import java.io.Reader
|
||||
import java.util.UUID
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input: InputStream, output: OutputStream, remoteAddress: BluetoothDevice, deviceInfo: DeviceInfo, linkProvider: BluetoothLinkProvider) : BaseLink(context!!, linkProvider) {
|
||||
private val connection: ConnectionMultiplexer?
|
||||
private val input: InputStream
|
||||
private val output: OutputStream
|
||||
private val remoteAddress: BluetoothDevice
|
||||
private val linkProvider: BluetoothLinkProvider
|
||||
private val deviceInfo: DeviceInfo
|
||||
class BluetoothLink(
|
||||
context: Context?,
|
||||
connection: ConnectionMultiplexer,
|
||||
val input: InputStream,
|
||||
val output: OutputStream,
|
||||
val remoteAddress: BluetoothDevice,
|
||||
val theDeviceInfo: DeviceInfo,
|
||||
val linkProvider: BluetoothLinkProvider
|
||||
) : BaseLink(context!!, linkProvider) {
|
||||
private val connection: ConnectionMultiplexer? = connection
|
||||
private var continueAccepting = true
|
||||
private val receivingThread = Thread(object : Runnable {
|
||||
override fun run() {
|
||||
@@ -64,8 +67,7 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
|
||||
private fun processMessage(message: String) {
|
||||
val np: NetworkPacket
|
||||
np = try {
|
||||
val np = try {
|
||||
NetworkPacket.unserialize(message)
|
||||
} catch (e: JSONException) {
|
||||
Log.e("BluetoothLink/receiving", "Unable to parse message.", e)
|
||||
@@ -84,15 +86,6 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
})
|
||||
|
||||
init {
|
||||
this.connection = connection
|
||||
this.input = input
|
||||
this.output = output
|
||||
this.deviceInfo = deviceInfo
|
||||
this.remoteAddress = remoteAddress
|
||||
this.linkProvider = linkProvider
|
||||
}
|
||||
|
||||
fun startListening() {
|
||||
receivingThread.start()
|
||||
}
|
||||
@@ -102,7 +95,7 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
|
||||
}
|
||||
|
||||
override fun getDeviceInfo(): DeviceInfo {
|
||||
return deviceInfo
|
||||
return theDeviceInfo
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
|
@@ -35,6 +35,7 @@ import java.io.Reader
|
||||
import java.security.cert.CertificateException
|
||||
import java.util.UUID
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
import androidx.core.content.edit
|
||||
|
||||
class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
private val visibleDevices: MutableMap<String, BluetoothLink> = HashMap()
|
||||
@@ -138,9 +139,9 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
} catch (e: SecurityException) {
|
||||
Log.e("KDEConnect", "Security Exception for CONNECT", e)
|
||||
|
||||
val prefenceEditor = PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
prefenceEditor.putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)
|
||||
prefenceEditor.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
private class ChannelInputStream constructor(val channel: Channel) : InputStream(), Closeable {
|
||||
private class ChannelInputStream(val channel: Channel) : InputStream(), Closeable {
|
||||
override fun available(): Int {
|
||||
return channel.available()
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private class ChannelOutputStream constructor(val channel: Channel) : OutputStream(), Closeable {
|
||||
private class ChannelOutputStream(val channel: Channel) : OutputStream(), Closeable {
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
channel.close()
|
||||
@@ -78,7 +78,7 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private class Channel constructor(val multiplexer: ConnectionMultiplexer, val id: UUID) : Closeable {
|
||||
private class Channel(val multiplexer: ConnectionMultiplexer, val id: UUID) : Closeable {
|
||||
val readBuffer: ByteBuffer = ByteBuffer.allocate(BUFFER_SIZE)
|
||||
val lock = ReentrantLock()
|
||||
var lockCondition: Condition = lock.newCondition()
|
||||
@@ -371,14 +371,9 @@ class ConnectionMultiplexer(socket: BluetoothSocket) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ListenRunnable constructor(socket: BluetoothSocket) : Runnable {
|
||||
var input: InputStream
|
||||
var output: OutputStream
|
||||
|
||||
init {
|
||||
input = socket.inputStream
|
||||
output = socket.outputStream
|
||||
}
|
||||
private inner class ListenRunnable(socket: BluetoothSocket) : Runnable {
|
||||
var input: InputStream = socket.inputStream
|
||||
var output: OutputStream = socket.outputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readBuffer(buffer: ByteArray, len: Int) {
|
||||
|
@@ -19,21 +19,18 @@ import androidx.annotation.WorkerThread;
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.DeviceHost;
|
||||
import org.kde.kdeconnect.DeviceInfo;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||
import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
|
||||
import org.kde.kdeconnect.KdeConnect;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsFragment;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
@@ -68,6 +65,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
final static int MAX_PORT = 1764;
|
||||
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
|
||||
|
||||
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;
|
||||
@@ -100,8 +98,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
NetworkPacket networkPacket;
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
String message = reader.readLine();
|
||||
String message = readSingleLine(socket);
|
||||
networkPacket = NetworkPacket.unserialize(message);
|
||||
//Log.e("TcpListener", "Received TCP packet: " + networkPacket.serialize());
|
||||
} catch (Exception e) {
|
||||
@@ -120,6 +117,25 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
identityPacketReceived(networkPacket, socket, LanLink.ConnectionStarted.Locally, deviceTrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single line from a socket without consuming anything else from the input.
|
||||
*/
|
||||
private String readSingleLine(Socket socket) throws IOException {
|
||||
InputStream stream = socket.getInputStream();
|
||||
StringBuilder line = new StringBuilder(MAX_IDENTITY_PACKET_SIZE);
|
||||
int ch;
|
||||
while ((ch = stream.read()) != -1) {
|
||||
line.append((char) ch);
|
||||
if (ch == '\n') {
|
||||
return line.toString();
|
||||
}
|
||||
if (line.length() >= MAX_IDENTITY_PACKET_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new IOException("Couldn't read a line from the socket");
|
||||
}
|
||||
|
||||
//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 {
|
||||
@@ -239,41 +255,40 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
final boolean clientMode = (connectionStarted == LanLink.ConnectionStarted.Locally);
|
||||
final SSLSocket sslSocket = SslHelper.convertToSslSocket(context, socket, deviceId, deviceTrusted, clientMode);
|
||||
sslSocket.addHandshakeCompletedListener(event -> {
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
NetworkPacket secureIdentityPacket;
|
||||
if (protocolVersion >= 8) {
|
||||
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||
NetworkPacket myIdentity = myDeviceInfo.toIdentityPacket();
|
||||
OutputStream writer = sslSocket.getOutputStream();
|
||||
writer.write(myIdentity.serialize().getBytes(Charsets.UTF_8));
|
||||
writer.flush();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
throw new JSONException("Can't read line");
|
||||
// Start a new thread because some Android versions don't allow calling sslSocket.getOutputStream() from the callback
|
||||
ThreadHelper.execute(() -> {
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
NetworkPacket secureIdentityPacket;
|
||||
if (protocolVersion >= 8) {
|
||||
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||
NetworkPacket myIdentity = myDeviceInfo.toIdentityPacket();
|
||||
OutputStream writer = sslSocket.getOutputStream();
|
||||
writer.write(myIdentity.serialize().getBytes(Charsets.UTF_8));
|
||||
writer.flush();
|
||||
String line = readSingleLine(sslSocket);
|
||||
// Do not trust the identity packet we received unencrypted
|
||||
secureIdentityPacket = NetworkPacket.unserialize(line);
|
||||
if (!DeviceInfo.isValidIdentityPacket(secureIdentityPacket)) {
|
||||
throw new JSONException("Invalid identity packet");
|
||||
}
|
||||
int newProtocolVersion = secureIdentityPacket.getInt("protocolVersion");
|
||||
if (newProtocolVersion != protocolVersion) {
|
||||
Log.w("KDE/LanLinkProvider", "Protocol version changed half-way through the handshake: " + protocolVersion + " ->" + newProtocolVersion);
|
||||
}
|
||||
} else {
|
||||
secureIdentityPacket = identityPacket;
|
||||
}
|
||||
// Do not trust the identity packet we received unencrypted
|
||||
secureIdentityPacket = NetworkPacket.unserialize(line);
|
||||
if (!DeviceInfo.isValidIdentityPacket(secureIdentityPacket)) {
|
||||
throw new JSONException("Invalid identity packet");
|
||||
}
|
||||
int newProtocolVersion = secureIdentityPacket.getInt("protocolVersion");
|
||||
if (newProtocolVersion != protocolVersion) {
|
||||
Log.w("KDE/LanLinkProvider", "Protocol version changed half-way through the handshake: " + protocolVersion + " ->" + newProtocolVersion);
|
||||
}
|
||||
} else {
|
||||
secureIdentityPacket = identityPacket;
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(secureIdentityPacket, certificate);
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + deviceName + " secured with " + event.getCipherSuite());
|
||||
addOrUpdateLink(sslSocket, deviceInfo);
|
||||
} catch (JSONException e) {
|
||||
Log.e("KDE/LanLinkProvider", "Remote device doesn't correctly implement protocol version 8", e);
|
||||
} catch (IOException e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + deviceName, e);
|
||||
}
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(secureIdentityPacket, certificate);
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + deviceName + " secured with " + event.getCipherSuite());
|
||||
addOrUpdateLink(sslSocket, deviceInfo);
|
||||
} catch (JSONException e) {
|
||||
Log.e("KDE/LanLinkProvider", "Remote device doesn't correctly implement protocol version 8", e);
|
||||
} catch (IOException e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + deviceName, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
|
||||
|
@@ -41,7 +41,7 @@ public class MdnsDiscovery {
|
||||
this.lanLinkProvider = lanLinkProvider;
|
||||
this.mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||
this.mNsdResolveQueue = new NsdResolveQueue(this.mNsdManager);
|
||||
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
multicastLock = wifiManager.createMulticastLock("kdeConnectMdnsMulticastLock");
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class MdnsDiscovery {
|
||||
// Also, on Android Lollipop those fields aren't resolved.
|
||||
String deviceName = DeviceHelper.getDeviceName(context);
|
||||
String deviceType = DeviceHelper.getDeviceType().toString();
|
||||
String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion);
|
||||
String protocolVersion = Integer.toString(DeviceHelper.PROTOCOL_VERSION);
|
||||
serviceInfo.setAttribute("id", deviceId);
|
||||
serviceInfo.setAttribute("name", deviceName);
|
||||
serviceInfo.setAttribute("type", deviceType);
|
||||
|
@@ -32,7 +32,6 @@ import org.kde.kdeconnect.Backends.BaseLinkProvider
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider.ConnectionReceiver
|
||||
import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider
|
||||
import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity
|
||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandActivity
|
||||
|
@@ -33,7 +33,6 @@ import org.kde.kdeconnect.DeviceStats.countReceived
|
||||
import org.kde.kdeconnect.DeviceStats.countSent
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||
import org.kde.kdeconnect.PairingHandler.PairingCallback
|
||||
import org.kde.kdeconnect.Plugins.Plugin
|
||||
import org.kde.kdeconnect.Plugins.Plugin.Companion.getPluginKey
|
||||
@@ -46,6 +45,7 @@ import java.util.Vector
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import androidx.core.content.edit
|
||||
|
||||
class Device : PacketReceiver {
|
||||
|
||||
@@ -164,7 +164,7 @@ class Device : PacketReceiver {
|
||||
|
||||
// Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
|
||||
fun compareProtocolVersion(): Int =
|
||||
deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion
|
||||
deviceInfo.protocolVersion - DeviceHelper.PROTOCOL_VERSION
|
||||
|
||||
val isPaired: Boolean
|
||||
get() = pairingHandler.state == PairingHandler.PairState.Paired
|
||||
@@ -209,7 +209,7 @@ class Device : PacketReceiver {
|
||||
|
||||
// Store as trusted device
|
||||
val preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE)
|
||||
preferences.edit().putBoolean(deviceInfo.id, true).apply()
|
||||
preferences.edit { putBoolean(deviceInfo.id, true) }
|
||||
|
||||
try {
|
||||
reloadPluginsFromSettings()
|
||||
@@ -228,10 +228,10 @@ class Device : PacketReceiver {
|
||||
override fun unpaired() {
|
||||
Log.i("Device", "unpaired, removing from trusted devices list")
|
||||
val preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE)
|
||||
preferences.edit().remove(deviceInfo.id).apply()
|
||||
preferences.edit { remove(deviceInfo.id) }
|
||||
|
||||
val devicePreferences = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE)
|
||||
devicePreferences.edit().clear().apply()
|
||||
devicePreferences.edit { clear() }
|
||||
|
||||
pairingCallbacks.forEach(PairingCallback::unpaired)
|
||||
|
||||
@@ -596,7 +596,7 @@ class Device : PacketReceiver {
|
||||
}
|
||||
|
||||
fun setPluginEnabled(pluginKey: String, value: Boolean) {
|
||||
settings.edit().putBoolean(pluginKey, value).apply()
|
||||
settings.edit { putBoolean(pluginKey, value) }
|
||||
reloadPluginsFromSettings()
|
||||
}
|
||||
|
||||
|
@@ -59,12 +59,12 @@ class DeviceInfo(
|
||||
*/
|
||||
fun toIdentityPacket(): NetworkPacket =
|
||||
NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY).also { np ->
|
||||
np.set("deviceId", id)
|
||||
np.set("deviceName", name)
|
||||
np.set("protocolVersion", protocolVersion)
|
||||
np.set("deviceType", type.toString())
|
||||
np.set("incomingCapabilities", incomingCapabilities!!)
|
||||
np.set("outgoingCapabilities", outgoingCapabilities!!)
|
||||
np["deviceId"] = id
|
||||
np["deviceName"] = name
|
||||
np["protocolVersion"] = protocolVersion
|
||||
np["deviceType"] = type.toString()
|
||||
np["incomingCapabilities"] = incomingCapabilities!!
|
||||
np["outgoingCapabilities"] = outgoingCapabilities!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -107,10 +107,10 @@ class DeviceInfo(
|
||||
fun isValidIdentityPacket(identityPacket: NetworkPacket): Boolean = with(identityPacket) {
|
||||
type == NetworkPacket.PACKET_TYPE_IDENTITY &&
|
||||
DeviceHelper.filterName(getString("deviceName", "")).isNotBlank() &&
|
||||
isValidDeviceId(getString("deviceId", ""));
|
||||
isValidDeviceId(getString("deviceId", ""))
|
||||
}
|
||||
|
||||
private val DEVICE_ID_REGEX = "^[a-zA-Z0-9_-]{32,38}\$".toRegex()
|
||||
private val DEVICE_ID_REGEX = "^[a-zA-Z0-9_-]{32,38}$".toRegex()
|
||||
|
||||
@JvmStatic
|
||||
fun isValidDeviceId(deviceId: String): Boolean = deviceId.matches(DEVICE_ID_REGEX)
|
||||
|
@@ -115,7 +115,7 @@ object DeviceStats {
|
||||
val entry = iterator.next()
|
||||
val events = entry.value
|
||||
|
||||
var index = Collections.binarySearch(events, cutoutTimestamp)
|
||||
var index = events.binarySearch(cutoutTimestamp)
|
||||
if (index < 0) {
|
||||
index = -(index + 1) // Convert the negative index to insertion point
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ public final class CollectionsBackport {
|
||||
}
|
||||
|
||||
static boolean eq(Object o1, Object o2) {
|
||||
return o1 == null ? o2 == null : o1.equals(o2);
|
||||
return Objects.equals(o1, o2);
|
||||
}
|
||||
|
||||
static class UnmodifiableNavigableSetBackport<E>
|
||||
|
@@ -214,7 +214,7 @@ public class ContactsHelper {
|
||||
|
||||
Map<uID, Map<String, String>> databaseValue = accessContactsDatabase(context, projection, selection, selectionArgs, null);
|
||||
|
||||
if (databaseValue.size() == 0) {
|
||||
if (databaseValue.isEmpty()) {
|
||||
throw new ContactNotFoundException("Querying for contact with id " + contactID + " returned no results.");
|
||||
}
|
||||
|
||||
|
27
src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
Normal file
27
src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.kde.kdeconnect.Helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
data class CreateFileParams(
|
||||
val fileMimeType: String,
|
||||
val suggestedFileName: String,
|
||||
)
|
||||
|
||||
class CreateFileResultContract : ActivityResultContract<CreateFileParams, Uri?>() {
|
||||
|
||||
override fun createIntent(context: Context, input: CreateFileParams): Intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
setTypeAndNormalize(input.fileMimeType)
|
||||
putExtra(Intent.EXTRA_TITLE, input.suggestedFileName)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? = when (resultCode) {
|
||||
Activity.RESULT_OK -> intent?.data
|
||||
else -> null
|
||||
}
|
||||
}
|
@@ -11,7 +11,6 @@ import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import com.univocity.parsers.common.TextParsingException
|
||||
import com.univocity.parsers.csv.CsvParser
|
||||
@@ -26,9 +25,10 @@ import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
import androidx.core.content.edit
|
||||
|
||||
object DeviceHelper {
|
||||
const val ProtocolVersion = 8
|
||||
const val PROTOCOL_VERSION = 8
|
||||
|
||||
const val KEY_DEVICE_NAME_PREFERENCE = "device_name_preference"
|
||||
private const val KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference"
|
||||
@@ -85,7 +85,7 @@ object DeviceHelper {
|
||||
|
||||
// If we get here we managed to download the file. Mark that as done so we don't try again even if we don't end up finding a name.
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply()
|
||||
preferences.edit { putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true) }
|
||||
|
||||
BufferedReader(
|
||||
InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16)
|
||||
@@ -124,26 +124,17 @@ object DeviceHelper {
|
||||
fun setDeviceName(context: Context, name: String) {
|
||||
val filteredName = filterName(name)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, filteredName).apply()
|
||||
preferences.edit { putString(KEY_DEVICE_NAME_PREFERENCE, filteredName) }
|
||||
}
|
||||
|
||||
fun initializeDeviceId(context: Context) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val preferenceKeys: Set<String> = preferences.all.keys
|
||||
if (preferenceKeys.contains(KEY_DEVICE_ID_PREFERENCE)) {
|
||||
val deviceId = preferences.getString(KEY_DEVICE_ID_PREFERENCE, "")!!
|
||||
if (DeviceInfo.isValidDeviceId(deviceId)) {
|
||||
return // We already have an ID
|
||||
}
|
||||
@SuppressLint("HardwareIds")
|
||||
val deviceName = if (preferenceKeys.isEmpty()) {
|
||||
// For new installations, use random IDs
|
||||
Log.i("DeviceHelper","No device ID found and this looks like a new installation, creating a random ID")
|
||||
UUID.randomUUID().toString().replace('-', '_')
|
||||
} else {
|
||||
// Use the ANDROID_ID as device ID for existing installations, for backwards compatibility
|
||||
Log.i("DeviceHelper", "No device ID found but this seems an existing installation, using the Android ID")
|
||||
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
}
|
||||
preferences.edit().putString(KEY_DEVICE_ID_PREFERENCE, deviceName).apply()
|
||||
val deviceName = UUID.randomUUID().toString().replace("-", "")
|
||||
preferences.edit { putString(KEY_DEVICE_ID_PREFERENCE, deviceName) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -159,7 +150,7 @@ object DeviceHelper {
|
||||
SslHelper.certificate,
|
||||
getDeviceName(context),
|
||||
deviceType,
|
||||
ProtocolVersion,
|
||||
PROTOCOL_VERSION,
|
||||
PluginFactory.incomingCapabilities,
|
||||
PluginFactory.outgoingCapabilities
|
||||
)
|
||||
|
@@ -97,7 +97,7 @@ object FilesHelper {
|
||||
|
||||
fun contentResolverExtract(): Triple<String?, Long, Long?> {
|
||||
// Since we used Intent.CATEGORY_OPENABLE, these two columns are the only ones we are guaranteed to have: https://developer.android.com/reference/android/provider/OpenableColumns
|
||||
val proj = arrayOf(OpenableColumns.SIZE, OpenableColumns.DISPLAY_NAME,)
|
||||
val proj = arrayOf(OpenableColumns.SIZE, OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
try {
|
||||
contentResolver.query(uri, proj, null, null, null).use { cursor ->
|
||||
|
@@ -11,7 +11,6 @@ import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.media.ThumbnailUtils
|
||||
import android.net.Uri
|
||||
@@ -23,6 +22,8 @@ import android.telephony.TelephonyManager
|
||||
import android.util.Log
|
||||
import android.util.Pair
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.mms.pdu_alt.MultimediaMessagePdu
|
||||
import com.google.android.mms.pdu_alt.PduPersister
|
||||
import com.google.android.mms.util_alt.PduCache
|
||||
@@ -37,13 +38,11 @@ import org.kde.kdeconnect.Helpers.TelephonyHelper.LocalPhoneNumber
|
||||
import org.kde.kdeconnect.Plugins.SMSPlugin.MimeType
|
||||
import org.kde.kdeconnect.Plugins.SMSPlugin.SmsMmsUtils
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
import java.util.Objects
|
||||
import java.util.SortedMap
|
||||
import java.util.TreeMap
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@@ -52,7 +51,7 @@ object SMSHelper {
|
||||
private const val THUMBNAIL_WIDTH = 100
|
||||
|
||||
// The constant Telephony.Mms.Part.CONTENT_URI was added in API 29
|
||||
val mMSPartUri : Uri = Uri.parse("content://mms/part/")
|
||||
val mMSPartUri : Uri = "content://mms/part/".toUri()
|
||||
|
||||
/**
|
||||
* Get the base address for all message conversations
|
||||
@@ -67,14 +66,14 @@ object SMSHelper {
|
||||
if ("Samsung".equals(Build.MANUFACTURER, ignoreCase = true)) {
|
||||
Log.i("SMSHelper", "This appears to be a Samsung device. This may cause some features to not work properly.")
|
||||
}
|
||||
return Uri.parse("content://mms-sms/conversations?simple=true")
|
||||
return "content://mms-sms/conversations?simple=true".toUri()
|
||||
}
|
||||
|
||||
private fun getCompleteConversationsUri(): Uri {
|
||||
// This glorious - but completely undocumented - content URI gives us all messages, both MMS and SMS,
|
||||
// in all conversations
|
||||
// See https://stackoverflow.com/a/36439630/3723163
|
||||
return Uri.parse("content://mms-sms/complete-conversations")
|
||||
return "content://mms-sms/complete-conversations".toUri()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,12 +650,7 @@ object SMSHelper {
|
||||
)
|
||||
val videoThumbnail = retriever.frameAtTime
|
||||
val encodedThumbnail = SmsMmsUtils.bitMapToBase64(
|
||||
Bitmap.createScaledBitmap(
|
||||
videoThumbnail!!,
|
||||
THUMBNAIL_WIDTH,
|
||||
THUMBNAIL_HEIGHT,
|
||||
true
|
||||
)
|
||||
videoThumbnail!!.scale(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
||||
)
|
||||
attachments.add(
|
||||
Attachment(
|
||||
|
@@ -24,7 +24,6 @@ import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.kde.kdeconnect.KdeConnect;
|
||||
|
@@ -6,7 +6,6 @@
|
||||
package org.kde.kdeconnect.Helpers
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
|
||||
object StorageHelper {
|
||||
fun getDisplayName(treeUri: Uri): String {
|
||||
|
@@ -13,6 +13,7 @@ import android.net.wifi.WifiManager
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
|
||||
class TrustedNetworkHelper(private val context: Context) {
|
||||
|
||||
@@ -22,19 +23,19 @@ class TrustedNetworkHelper(private val context: Context) {
|
||||
return serializedNetworks.split(NETWORK_SSID_DELIMITER, "#_#" /* TODO remove old delimiter in 2025 */).filter { it.isNotEmpty() }
|
||||
}
|
||||
set(value) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putString(KEY_CUSTOM_TRUSTED_NETWORKS, value.joinToString(NETWORK_SSID_DELIMITER))
|
||||
.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putString(
|
||||
KEY_CUSTOM_TRUSTED_NETWORKS,
|
||||
value.joinToString(NETWORK_SSID_DELIMITER)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var allNetworksAllowed: Boolean
|
||||
get() = !hasPermissions || PreferenceManager.getDefaultSharedPreferences(context).getBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, true)
|
||||
set(value) = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, value)
|
||||
.apply()
|
||||
set(value) = PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(KEY_CUSTOM_TRUST_ALL_NETWORKS, value)
|
||||
}
|
||||
|
||||
val hasPermissions: Boolean
|
||||
get() = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||
|
@@ -25,6 +25,7 @@ import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import androidx.core.content.edit
|
||||
|
||||
/*
|
||||
* This class holds all the active devices and makes them accessible from every other class.
|
||||
@@ -116,7 +117,7 @@ class KdeConnect : Application() {
|
||||
"KdeConnect",
|
||||
"Couldn't load the certificate for a remembered device. Removing from trusted list.", e
|
||||
)
|
||||
preferences.edit().remove(it).apply()
|
||||
preferences.edit { remove(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +128,7 @@ class KdeConnect : Application() {
|
||||
trustedDevices.filter { preferences.getBoolean(it, false) }
|
||||
.forEach {
|
||||
Log.d("KdeConnect", "Removing devices: $it")
|
||||
preferences.edit().remove(it).apply()
|
||||
preferences.edit { remove(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,9 +11,7 @@ import org.json.JSONObject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.net.Socket
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
class NetworkPacket private constructor(
|
||||
val type: String,
|
||||
|
@@ -25,8 +25,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class BigscreenPlugin extends Plugin {
|
||||
|
||||
|
@@ -25,8 +25,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class ClipboardPlugin extends Plugin {
|
||||
|
||||
|
@@ -37,7 +37,6 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class FindMyPhonePlugin extends Plugin {
|
||||
|
@@ -19,8 +19,6 @@ import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class MousePadPlugin extends Plugin {
|
||||
|
||||
|
@@ -157,9 +157,6 @@ public class PointerAccelerationProfileFactory {
|
||||
|
||||
public static PointerAccelerationProfile getProfileWithName(String name) {
|
||||
switch (name) {
|
||||
case "noacceleration":
|
||||
default:
|
||||
return new DefaultProfile();
|
||||
case "weaker":
|
||||
return new PolynomialProfile(0.25f);
|
||||
case "weak":
|
||||
@@ -170,6 +167,9 @@ public class PointerAccelerationProfileFactory {
|
||||
return new PolynomialProfile(1.5f);
|
||||
case "stronger":
|
||||
return new PolynomialProfile(2.0f);
|
||||
case "noacceleration":
|
||||
default:
|
||||
return new DefaultProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ import java.net.URL
|
||||
import java.net.URLDecoder
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import androidx.core.net.toUri
|
||||
|
||||
/**
|
||||
* Handles the cache for album art
|
||||
@@ -130,7 +131,7 @@ internal object AlbumArtCache {
|
||||
if (albumUrl.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
val url = Uri.parse(albumUrl)
|
||||
val url = albumUrl.toUri()
|
||||
|
||||
//We currently only support http(s), file, and kdeconnect urls
|
||||
if (url.scheme !in ALLOWED_SCHEMES) {
|
||||
@@ -221,7 +222,7 @@ internal object AlbumArtCache {
|
||||
* Does the actual fetching and makes sure only not too many fetches are running at the same time
|
||||
*/
|
||||
private fun initiateFetch() {
|
||||
var url : Uri;
|
||||
var url : Uri
|
||||
synchronized(fetchUrlList) {
|
||||
if (numFetching >= 2 || fetchUrlList.isEmpty()) return
|
||||
//Fetch the last-requested url first, it will probably be needed first
|
||||
@@ -283,7 +284,7 @@ internal object AlbumArtCache {
|
||||
payload.close()
|
||||
return
|
||||
}
|
||||
val url = Uri.parse(albumUrl)
|
||||
val url = albumUrl.toUri()
|
||||
if (url.scheme !in REMOTE_FETCH_SCHEMES) {
|
||||
//Shouldn't happen (checked on receival of the url), but just to be sure
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "Got invalid art url with payload: $albumUrl")
|
||||
|
@@ -34,6 +34,7 @@ import org.kde.kdeconnect_tp.databinding.MprisNowPlayingBinding
|
||||
import java.net.MalformedURLException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private typealias MprisPlayerCallback = (MprisPlayer) -> Unit
|
||||
|
||||
@@ -370,7 +371,7 @@ class MprisNowPlayingFragment : Fragment(), VolumeKeyListener {
|
||||
if (targetPlayer != null && item.itemId == MENU_OPEN_URL) {
|
||||
try {
|
||||
val url = VideoUrlsHelper.formatUriWithSeek(targetPlayer.url, targetPlayer.position).toString()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(browserIntent)
|
||||
targetPlayer.sendPause()
|
||||
return true
|
||||
|
@@ -33,6 +33,7 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import java.net.MalformedURLException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@LoadablePlugin
|
||||
class MprisPlugin : Plugin() {
|
||||
@@ -273,7 +274,7 @@ class MprisPlugin : Plugin() {
|
||||
playerStatus.isGoPreviousAllowed = np.getBoolean("canGoPrevious", playerStatus.isGoPreviousAllowed)
|
||||
playerStatus.seekAllowed = np.getBoolean("canSeek", playerStatus.seekAllowed)
|
||||
val newAlbumArtUrlString = np.getString("albumArtUrl", playerStatus.albumArtUrl)
|
||||
val newAlbumArtUrl = Uri.parse(newAlbumArtUrlString)
|
||||
val newAlbumArtUrl = newAlbumArtUrlString.toUri()
|
||||
if (newAlbumArtUrl.scheme in AlbumArtCache.ALLOWED_SCHEMES) {
|
||||
playerStatus.albumArtUrl = newAlbumArtUrl.toString()
|
||||
} else {
|
||||
@@ -334,7 +335,7 @@ class MprisPlugin : Plugin() {
|
||||
) {
|
||||
try {
|
||||
val url = VideoUrlsHelper.formatUriWithSeek(playerStatus.url, playerStatus.position).toString()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, browserIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
val notificationManager = ContextCompat.getSystemService(context, NotificationManager::class.java)
|
||||
@@ -350,7 +351,7 @@ class MprisPlugin : Plugin() {
|
||||
builder.build()
|
||||
)
|
||||
} catch (e: MalformedURLException) {
|
||||
e.printStackTrace();
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.content.getSystemService
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper
|
||||
import org.kde.kdeconnect.NetworkPacket
|
||||
|
@@ -24,8 +24,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class PresenterPlugin extends Plugin {
|
||||
|
||||
|
@@ -29,7 +29,6 @@ import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class ReceiveNotificationsPlugin extends Plugin {
|
||||
|
@@ -67,7 +67,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
}
|
||||
|
||||
public static boolean isConnected() {
|
||||
return instances.size() > 0;
|
||||
return !instances.isEmpty();
|
||||
}
|
||||
|
||||
private static final SparseIntArray specialKeyMap = new SparseIntArray();
|
||||
@@ -135,7 +135,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
try {
|
||||
if (instances.contains(this)) {
|
||||
instances.remove(this);
|
||||
if (instances.size() < 1 && RemoteKeyboardService.instance != null)
|
||||
if (instances.isEmpty() && RemoteKeyboardService.instance != null)
|
||||
RemoteKeyboardService.instance.handler.post(() -> RemoteKeyboardService.instance.updateInputView());
|
||||
}
|
||||
} finally {
|
||||
@@ -348,7 +348,7 @@ public class RemoteKeyboardPlugin extends Plugin implements SharedPreferences.On
|
||||
public enum MousePadPacketType {
|
||||
Keyboard,
|
||||
Mouse,
|
||||
};
|
||||
}
|
||||
|
||||
public static MousePadPacketType getMousePadPacketType(NetworkPacket np) {
|
||||
if (np.has("key") || np.has("specialKey")) {
|
||||
|
@@ -165,7 +165,7 @@ public class RemoteKeyboardService
|
||||
intent.putExtra(MainActivity.FLAG_FORCE_OVERVIEW, true);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
if (instances.size() < 1)
|
||||
if (instances.isEmpty())
|
||||
Toast.makeText(this, R.string.remotekeyboard_not_connected, Toast.LENGTH_SHORT).show();
|
||||
else // instances.size() > 1
|
||||
Toast.makeText(this, R.string.remotekeyboard_multiple_connections, Toast.LENGTH_SHORT).show();
|
||||
|
@@ -130,7 +130,7 @@ public class RunCommandActivity extends BaseActivity<ActivityRunCommandBinding>
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
if (item.getItemId() == R.id.copy_url_to_clipboard) {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(info.position);
|
||||
CommandEntry entry = commandItems.get(info.position);
|
||||
String url = "kdeconnect://runcommand/" + deviceId + "/" + entry.getKey();
|
||||
ClipboardManager cm = ContextCompat.getSystemService(this, ClipboardManager.class);
|
||||
cm.setText(url);
|
||||
|
@@ -34,7 +34,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
public class RunCommandPlugin extends Plugin {
|
||||
|
@@ -18,12 +18,12 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import org.kde.kdeconnect.Device
|
||||
import org.kde.kdeconnect.KdeConnect
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.WidgetRemoteCommandPluginDialogBinding
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.streams.toList
|
||||
|
||||
class RunCommandWidgetConfigActivity : AppCompatActivity() {
|
||||
|
||||
@@ -45,7 +45,7 @@ class RunCommandWidgetConfigActivity : AppCompatActivity() {
|
||||
val binding = WidgetRemoteCommandPluginDialogBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val pairedDevices = KdeConnect.getInstance().devices.values.stream().filter(Device::isPaired).collect(Collectors.toList());
|
||||
val pairedDevices = KdeConnect.getInstance().devices.values.stream().filter(Device::isPaired).collect(Collectors.toList())
|
||||
|
||||
binding.runCommandsDeviceList.adapter = object : ArrayAdapter<Device>(this, 0, pairedDevices) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
@@ -76,9 +76,9 @@ private const val PREFS_NAME = "org.kde.kdeconnect_tp.WidgetProvider"
|
||||
private const val PREF_PREFIX_KEY = "appwidget_"
|
||||
|
||||
internal fun saveWidgetDeviceIdPref(context: Context, appWidgetId: Int, deviceName: String) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||
prefs.putString(PREF_PREFIX_KEY + appWidgetId, deviceName)
|
||||
prefs.apply()
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||
putString(PREF_PREFIX_KEY + appWidgetId, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadWidgetDeviceIdPref(context: Context, appWidgetId: Int): String? {
|
||||
@@ -87,7 +87,7 @@ internal fun loadWidgetDeviceIdPref(context: Context, appWidgetId: Int): String?
|
||||
}
|
||||
|
||||
internal fun deleteWidgetDeviceIdPref(context: Context, appWidgetId: Int) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||
prefs.remove(PREF_PREFIX_KEY + appWidgetId)
|
||||
prefs.apply()
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||
remove(PREF_PREFIX_KEY + appWidgetId)
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import org.kde.kdeconnect.Device
|
||||
import org.kde.kdeconnect.KdeConnect
|
||||
import org.kde.kdeconnect_tp.BuildConfig
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import androidx.core.net.toUri
|
||||
|
||||
const val RUN_COMMAND_ACTION = "RUN_COMMAND_ACTION"
|
||||
const val TARGET_COMMAND = "TARGET_COMMAND"
|
||||
@@ -65,10 +66,10 @@ class RunCommandWidgetProvider : AppWidgetProvider() {
|
||||
Log.e("RunCommandWidget", "Error running command", ex)
|
||||
}
|
||||
} else {
|
||||
Log.w("RunCommandWidget", "Device not available or runcommand plugin disabled");
|
||||
Log.w("RunCommandWidget", "Device not available or runcommand plugin disabled")
|
||||
}
|
||||
} else {
|
||||
super.onReceive(context, intent);
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +115,7 @@ internal fun updateAppWidget(
|
||||
val views = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_remotecommandplugin)
|
||||
assignTitleIntent(context, appWidgetId, views)
|
||||
|
||||
Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name)
|
||||
Log.d("WidgetProvider", "updateAppWidget device: " + (device?.name ?: "null"))
|
||||
|
||||
// Android should automatically toggle between the command list and the error text
|
||||
views.setEmptyView(R.id.widget_command_list, R.id.widget_error_text)
|
||||
@@ -174,7 +175,7 @@ private fun assignTitleIntent(context: Context, appWidgetId: Int, views: RemoteV
|
||||
private fun assignListAdapter(context: Context, appWidgetId: Int, views: RemoteViews) {
|
||||
val dataProviderIntent = Intent(context, CommandsRemoteViewsService::class.java)
|
||||
dataProviderIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
dataProviderIntent.data = Uri.parse(dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
dataProviderIntent.data = dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME).toUri()
|
||||
views.setRemoteAdapter(R.id.widget_command_list, dataProviderIntent)
|
||||
}
|
||||
|
||||
|
@@ -181,7 +181,7 @@ class SMSPlugin : Plugin() {
|
||||
@Deprecated("")
|
||||
private fun smsBroadcastReceivedDeprecated(messages: MutableList<SmsMessage>) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (messages.size <= 0) {
|
||||
if (messages.isEmpty()) {
|
||||
throw AssertionError("This method requires at least one message")
|
||||
}
|
||||
}
|
||||
@@ -224,11 +224,11 @@ class SMSPlugin : Plugin() {
|
||||
get() = R.string.telepathy_permission_explanation
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
val filter: IntentFilter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
|
||||
val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
|
||||
filter.priority = 500
|
||||
context.registerReceiver(receiver, filter)
|
||||
|
||||
val refreshFilter: IntentFilter = IntentFilter(Transaction.REFRESH)
|
||||
val refreshFilter = IntentFilter(Transaction.REFRESH)
|
||||
refreshFilter.priority = 500
|
||||
context.registerReceiver(messagesUpdateReceiver, refreshFilter, ContextCompat.RECEIVER_EXPORTED)
|
||||
|
||||
@@ -269,13 +269,11 @@ class SMSPlugin : Plugin() {
|
||||
val subID = np.getLong("subID", -1)
|
||||
|
||||
val jsonAddressList = np.getJSONArray("addresses")
|
||||
val addressList: List<SMSHelper.Address>
|
||||
if (jsonAddressList == null) {
|
||||
val addressList = if (jsonAddressList == null) {
|
||||
// If jsonAddressList is null, then the SMS_REQUEST packet is most probably from the older version of the desktop app.
|
||||
addressList = listOf(SMSHelper.Address(context, np.getString("phoneNumber")))
|
||||
}
|
||||
else {
|
||||
addressList = jsonArrayToAddressList(context, jsonAddressList)
|
||||
listOf(SMSHelper.Address(context, np.getString("phoneNumber")))
|
||||
} else {
|
||||
jsonArrayToAddressList(context, jsonAddressList)
|
||||
}
|
||||
val attachedFiles: List<SMSHelper.Attachment> = jsonArrayToAttachmentsList(np.getJSONArray("attachments"))
|
||||
|
||||
@@ -350,11 +348,10 @@ class SMSPlugin : Plugin() {
|
||||
numberToGet = null
|
||||
}
|
||||
|
||||
val conversation: List<SMSHelper.Message>
|
||||
if (rangeStartTimestamp < 0) {
|
||||
conversation = getMessagesInThread(this.context, threadID, numberToGet)
|
||||
val conversation = if (rangeStartTimestamp < 0) {
|
||||
getMessagesInThread(this.context, threadID, numberToGet)
|
||||
} else {
|
||||
conversation = getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet, true)
|
||||
getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet, true)
|
||||
}
|
||||
|
||||
val reply: NetworkPacket = constructBulkMessagePacket(conversation)
|
||||
|
@@ -11,10 +11,10 @@ import android.content.ContentResolver
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.Settings
|
||||
import androidx.core.net.toUri
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.kde.kdeconnect.Helpers.NetworkHelper.localIpAddress
|
||||
@@ -44,7 +44,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
return if (SimpleSftpServer.SUPPORTS_NATIVEFS) {
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
SftpSettingsFragment.getStorageInfoList(context, this).size != 0
|
||||
SftpSettingsFragment.getStorageInfoList(context, this).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
} else {
|
||||
val storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this)
|
||||
storageInfoList.sortBy { it.uri }
|
||||
if (storageInfoList.size <= 0) {
|
||||
if (storageInfoList.isEmpty()) {
|
||||
device.sendPacket(NetworkPacket(PACKET_TYPE_SFTP).apply {
|
||||
this["errorMessage"] = context.getString(R.string.sftp_no_storage_locations_configured)
|
||||
})
|
||||
@@ -127,7 +127,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
this["password"] = server.regeneratePassword()
|
||||
// Kept for compatibility, in case "multiPaths" is not possible or the other end does not support it
|
||||
this["path"] = if (paths.size == 1) paths[0] else "/"
|
||||
if (paths.size > 0) {
|
||||
if (paths.isNotEmpty()) {
|
||||
this["multiPaths"] = paths
|
||||
this["pathNames"] = pathNames
|
||||
}
|
||||
@@ -240,7 +240,7 @@ class SftpPlugin : Plugin(), OnSharedPreferenceChangeListener {
|
||||
@Throws(JSONException::class)
|
||||
fun fromJSON(jsonObject: JSONObject): StorageInfo { // TODO: Use Result after migrate callee to Kotlin
|
||||
val displayName = jsonObject.getString(KEY_DISPLAY_NAME)
|
||||
val uri = Uri.parse(jsonObject.getString(KEY_URI))
|
||||
val uri = jsonObject.getString(KEY_URI).toUri()
|
||||
|
||||
return StorageInfo(displayName, uri)
|
||||
}
|
||||
|
@@ -48,6 +48,7 @@ import java.security.KeyPair
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.PublicKey
|
||||
import androidx.core.net.toUri
|
||||
|
||||
internal class SimpleSftpServer {
|
||||
private lateinit var sshd: SshServer
|
||||
@@ -110,7 +111,7 @@ internal class SimpleSftpServer {
|
||||
withFileSystemAccessor(object : SftpFileSystemAccessor {
|
||||
fun notifyMediaStore(path: Path) {
|
||||
kotlin.runCatching {
|
||||
val uri = Uri.parse(path.toUri().toString())
|
||||
val uri = path.toUri().toString().toUri()
|
||||
MediaStoreHelper.indexFile(context, uri)
|
||||
uri
|
||||
}.fold(
|
||||
|
@@ -5,14 +5,12 @@
|
||||
*/
|
||||
package org.kde.kdeconnect.Plugins.SftpPlugin.saf
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Method
|
||||
|
@@ -17,7 +17,6 @@ import android.webkit.URLUtil;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
|
@@ -20,7 +20,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@PluginFactory.LoadablePlugin
|
||||
|
@@ -34,7 +34,6 @@ import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
@@ -21,6 +21,7 @@ import org.kde.kdeconnect.UserInterface.MainActivity
|
||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class AboutFragment : Fragment() {
|
||||
private var _binding: FragmentAboutBinding? = null
|
||||
@@ -113,7 +114,7 @@ class AboutFragment : Fragment() {
|
||||
button.visibility = View.GONE
|
||||
} else {
|
||||
button.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
startActivity(Intent(Intent.ACTION_VIEW, url.toUri()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import androidx.appcompat.widget.TooltipCompat
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.AboutPersonListItemEntryBinding
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class AboutPersonEntryItem(val person: AboutPerson) : ListAdapter.Item {
|
||||
override fun inflateView(layoutInflater: LayoutInflater): View {
|
||||
@@ -31,7 +32,8 @@ class AboutPersonEntryItem(val person: AboutPerson) : ListAdapter.Item {
|
||||
binding.aboutPersonListItemEntryVisitHomepageButton.visibility = View.VISIBLE
|
||||
TooltipCompat.setTooltipText(binding.aboutPersonListItemEntryVisitHomepageButton, layoutInflater.context.resources.getString(R.string.visit_contributors_homepage, person.webAddress))
|
||||
binding.aboutPersonListItemEntryVisitHomepageButton.setOnClickListener {
|
||||
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(person.webAddress)))
|
||||
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW,
|
||||
person.webAddress.toUri()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -194,7 +194,7 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
saveList();
|
||||
showEmptyListMessageIfRequired();
|
||||
})
|
||||
.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
.addCallback(new BaseTransientBottomBar.BaseCallback<>() {
|
||||
@Override
|
||||
public void onDismissed(Snackbar transientBottomBar, int event) {
|
||||
switch (event) {
|
||||
@@ -250,11 +250,17 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
showEmptyListMessageIfRequired();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_duplicate, Toast.LENGTH_SHORT).show();
|
||||
Context context = addDeviceDialog.getContext();
|
||||
if (context != null) {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_duplicate, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_invalid, Toast.LENGTH_SHORT).show();
|
||||
Context context = addDeviceDialog.getContext();
|
||||
if (context != null) {
|
||||
Toast.makeText(addDeviceDialog.getContext(), R.string.device_host_invalid, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,8 +272,8 @@ public class CustomDevicesActivity extends BaseActivity<ActivityCustomDevicesBin
|
||||
}
|
||||
|
||||
private static class DeletedCustomDevice {
|
||||
@NonNull DeviceHost hostnameOrIP;
|
||||
int position;
|
||||
@NonNull final DeviceHost hostnameOrIP;
|
||||
final int position;
|
||||
|
||||
DeletedCustomDevice(@NonNull DeviceHost hostnameOrIP, int position) {
|
||||
this.hostnameOrIP = hostnameOrIP;
|
||||
|
@@ -39,7 +39,9 @@ import org.kde.kdeconnect.UserInterface.About.AboutFragment
|
||||
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
|
||||
import org.kde.kdeconnect_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
|
||||
import java.util.LinkedList
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.size
|
||||
import androidx.core.view.get
|
||||
|
||||
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
|
||||
private const val MENU_ENTRY_SETTINGS = 2
|
||||
@@ -59,7 +61,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
private var mCurrentDevice: String? = null
|
||||
private var mCurrentMenuEntry = 0
|
||||
private set(value) {
|
||||
set(value) {
|
||||
field = value
|
||||
//Enabling "go to default fragment on back" callback when user in settings or "about" fragment
|
||||
mainFragmentCallback.isEnabled = value == MENU_ENTRY_SETTINGS || value == MENU_ENTRY_ABOUT
|
||||
@@ -87,7 +89,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
val root = binding.root
|
||||
setContentView(root)
|
||||
mDrawerLayout = if (root is DrawerLayout) root else null
|
||||
mDrawerLayout = root as? DrawerLayout
|
||||
|
||||
val mDrawerHeader = mNavigationView.getHeaderView(0)
|
||||
mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name)
|
||||
@@ -115,17 +117,17 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
when (mCurrentMenuEntry) {
|
||||
MENU_ENTRY_ADD_DEVICE -> {
|
||||
mCurrentDevice = null
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(PairingFragment())
|
||||
}
|
||||
|
||||
MENU_ENTRY_SETTINGS -> {
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(SettingsFragment())
|
||||
}
|
||||
|
||||
MENU_ENTRY_ABOUT -> {
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, null) }
|
||||
setContentFragment(AboutFragment.newInstance(getApplicationAboutData(this)))
|
||||
}
|
||||
|
||||
@@ -207,7 +209,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
if(missingPermissions.size > 0){
|
||||
if(missingPermissions.isNotEmpty()){
|
||||
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), RESULT_NOTIFICATIONS_ENABLED)
|
||||
}
|
||||
}
|
||||
@@ -303,7 +305,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
@JvmOverloads
|
||||
fun onDeviceSelected(deviceId: String?, fromDeviceList: Boolean = false) {
|
||||
mCurrentDevice = deviceId
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply()
|
||||
preferences.edit { putString(STATE_SELECTED_DEVICE, deviceId) }
|
||||
if (mCurrentDevice != null) {
|
||||
mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId)
|
||||
if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) {
|
||||
@@ -364,9 +366,9 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
if (isPermissionGranted(permissions, grantResults, Manifest.permission.BLUETOOTH_CONNECT) &&
|
||||
isPermissionGranted(permissions, grantResults, Manifest.permission.BLUETOOTH_SCAN)) {
|
||||
val preferenceEditor = PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||
preferenceEditor.putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, true)
|
||||
preferenceEditor.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit {
|
||||
putBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, true)
|
||||
}
|
||||
setContentFragment(SettingsFragment())
|
||||
}
|
||||
|
||||
@@ -390,9 +392,9 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
|
||||
private fun uncheckAllMenuItems(menu: Menu) {
|
||||
val size = menu.size()
|
||||
val size = menu.size
|
||||
for (i in 0 until size) {
|
||||
val item = menu.getItem(i)
|
||||
val item = menu[i]
|
||||
item.subMenu?.let { uncheckAllMenuItems(it) } ?: item.setChecked(false)
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
@@ -57,6 +66,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
devicesByIpPref(context),
|
||||
udpBroadcastPref(context),
|
||||
bluetoothSupportPref(context),
|
||||
exportLogsPref(context),
|
||||
moreSettingsPref(context),
|
||||
).forEach(screen::addPreference)
|
||||
|
||||
@@ -218,6 +228,32 @@ 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 pid = android.os.Process.myPid()
|
||||
val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d", "--pid=$pid"))
|
||||
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
|
||||
|
@@ -6,9 +6,8 @@
|
||||
package org.kde.kdeconnect.UserInterface
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class StartActivityAlertDialogFragment : AlertDialogFragment() {
|
||||
private var intentAction: String? = null
|
||||
@@ -36,8 +35,9 @@ class StartActivityAlertDialogFragment : AlertDialogFragment() {
|
||||
|
||||
setCallback(object : Callback() {
|
||||
override fun onPositiveButtonClicked() {
|
||||
val intentUrl = intentUrl
|
||||
val intent = if (!intentUrl.isNullOrEmpty()) {
|
||||
Intent(intentAction, Uri.parse(intentUrl))
|
||||
Intent(intentAction, intentUrl.toUri())
|
||||
} else {
|
||||
Intent(intentAction)
|
||||
}
|
||||
|
@@ -9,8 +9,11 @@ package org.kde.kdeconnect.UserInterface.compose
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@@ -6,7 +6,6 @@
|
||||
package org.kde.kdeconnect.async
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
abstract class BackgroundJob<I, R> : Runnable {
|
||||
private val callback: Callback<R>
|
||||
|
@@ -129,7 +129,7 @@ class DeviceTest {
|
||||
val deviceId = "testDevice"
|
||||
val settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE)
|
||||
val deviceInfo = loadFromSettings(context, deviceId, settings)
|
||||
deviceInfo.protocolVersion = DeviceHelper.ProtocolVersion
|
||||
deviceInfo.protocolVersion = DeviceHelper.PROTOCOL_VERSION
|
||||
deviceInfo.incomingCapabilities = hashSetOf("kdeconnect.plugin1State", "kdeconnect.plugin2State")
|
||||
deviceInfo.outgoingCapabilities = hashSetOf("kdeconnect.plugin1State.request", "kdeconnect.plugin2State.request")
|
||||
|
||||
@@ -208,7 +208,7 @@ class DeviceTest {
|
||||
val deviceId = "unpairedTestDevice"
|
||||
fakeNetworkPacket["deviceId"] = deviceId
|
||||
fakeNetworkPacket["deviceName"] = "Unpaired Test Device"
|
||||
fakeNetworkPacket["protocolVersion"] = DeviceHelper.ProtocolVersion
|
||||
fakeNetworkPacket["protocolVersion"] = DeviceHelper.PROTOCOL_VERSION
|
||||
fakeNetworkPacket["deviceType"] = DeviceType.PHONE.toString()
|
||||
val certificateString =
|
||||
"""
|
||||
|
@@ -12,7 +12,6 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||
import org.kde.kdeconnect.MockSharedPreference
|
||||
import org.kde.kdeconnect.PairingHandler
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.MockedStatic
|
||||
import org.mockito.Mockito
|
||||
|
Reference in New Issue
Block a user