mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-01 14:45:08 +00:00
Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
106e651c6c | ||
|
5669e3c22c | ||
|
2d1dbd124c | ||
|
1edcbd2b39 | ||
|
c76a32ff05 | ||
|
c078e45a98 | ||
|
163e3c31f4 | ||
|
5a5e236710 | ||
|
603c87d42d | ||
|
1a04bfbbea | ||
|
fec0b34330 | ||
|
f6c4084746 | ||
|
968d018f41 | ||
|
4fc6ca8d4f | ||
|
d3ab18b721 | ||
|
b2d0e57641 | ||
|
66238406d6 | ||
|
010c960680 | ||
|
49d4383828 | ||
|
226869e200 | ||
|
bbc1113710 | ||
|
b3bacf241c | ||
|
ff47313409 | ||
|
ac4f072322 | ||
|
5ed1b80716 | ||
|
8a413bb42e | ||
|
71dc713578 | ||
|
ca3d677db6 | ||
|
79ed37345b | ||
|
24c404400f | ||
|
082de423c0 | ||
|
b5fb7f73ee | ||
|
097d1f5fa5 | ||
|
5fa43f8979 | ||
|
e5c7adba3a | ||
|
ced5c71369 | ||
|
9bf2adefc4 | ||
|
24685348cf | ||
|
7556e1d7fa | ||
|
c9b852f88c | ||
|
d8169f3787 | ||
|
b71e8562bc | ||
|
4d19a7cdc8 | ||
|
ae7a80e262 | ||
|
4a269a607c | ||
|
a6c23b252c | ||
|
fa5f584fe4 | ||
|
634f71d724 | ||
|
055e0f6454 | ||
|
8df2b7b0ee | ||
|
5f64e94878 | ||
|
abbbc9414d | ||
|
cfa13cb16b | ||
|
d726fe23bf | ||
|
d920b2659d | ||
|
0836453c35 | ||
|
09e1811bd8 | ||
|
1ab07d201e | ||
|
1c2dfdb795 | ||
|
dd3df9d19f | ||
|
5bfbd90414 | ||
|
5eb17f7a9a | ||
|
44be314899 | ||
|
728fa0b508 | ||
|
e9e93423f1 | ||
|
a1dd4fe2cc | ||
|
2294f30505 | ||
|
8c86edcf33 | ||
|
113739e1d9 | ||
|
48cd5aa29b | ||
|
5487c5556b | ||
|
e34de854bf | ||
|
d8aab59d4b | ||
|
835a4f0bc7 | ||
|
a588c8f787 | ||
|
36d1145df7 | ||
|
5d3ccbe0b0 | ||
|
2497ec2b1c | ||
|
bb8f015e87 | ||
|
f3e5bf917a | ||
|
45ba30e0ba | ||
|
f967c2888d | ||
|
6f0b581c02 |
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="1000"
|
||||
android:versionName="1.0">
|
||||
android:versionCode="1200"
|
||||
android:versionName="1.2">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9"
|
||||
android:targetSdkVersion="22" />
|
||||
@@ -31,6 +31,7 @@
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:supportsRtl="true"
|
||||
android:label="KDE Connect"
|
||||
android:theme="@style/KdeConnectTheme"
|
||||
>
|
||||
|
33
build.gradle
33
build.gradle
@@ -3,22 +3,23 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.5.0'
|
||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
buildToolsVersion '23.0.2'
|
||||
buildToolsVersion '23.0.3'
|
||||
compileSdkVersion 23
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 22 //Bumping to 23 means we have to support the new permissions model
|
||||
multiDexEnabled true
|
||||
//multiDexEnabled true
|
||||
//testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
|
||||
}
|
||||
dexOptions {
|
||||
javaMaxHeapSize "4g"
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
compileOptions {
|
||||
// Use Java 1.7, requires minSdk 8
|
||||
@@ -52,31 +53,35 @@ android {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
}
|
||||
release { //keep on 'releae', set to 'all' when testing to make sure proguard is not deleting important stuff
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
compile 'com.android.support:support-v4:23.2.1'
|
||||
compile 'com.android.support:appcompat-v7:23.2.1'
|
||||
compile 'com.android.support:design:23.2.1'
|
||||
|
||||
compile 'com.android.support:support-v4:23.4.0'
|
||||
compile 'com.android.support:appcompat-v7:23.4.0'
|
||||
compile 'com.android.support:design:23.4.0'
|
||||
|
||||
compile 'org.apache.sshd:sshd-core:0.8.0' //0.9 seems to fail on Android 6 and 1.+ requires java.nio.file, which doesn't exist in Android
|
||||
//compile 'org.bouncycastle:bcprov-jdk15on:1.54'
|
||||
|
||||
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
|
||||
compile 'io.netty:netty-handler:4.1.0.CR3'
|
||||
compile 'com.madgag.spongycastle:pkix:1.54.0.0' //For SSL certificate generation
|
||||
|
||||
// Testing
|
||||
androidTestCompile 'org.mockito:mockito-core:1.10.19'
|
||||
// Because mockito has some problems with dex environment
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'// Because mockito has some problems with dex environment
|
||||
androidTestCompile 'org.skyscreamer:jsonassert:1.3.0'
|
||||
|
||||
//compile fileTree(include: '*.jar', dir: 'libs')
|
||||
}
|
||||
|
5
proguard-rules.pro
vendored
5
proguard-rules.pro
vendored
@@ -24,7 +24,6 @@
|
||||
-keepnames class !android.support.v7.internal.view.menu.**,android.support.v7.** {*;}
|
||||
|
||||
-dontwarn org.spongycastle.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.apache.sshd.**
|
||||
-dontwarn org.apache.mina.**
|
||||
-dontwarn org.slf4j.**
|
||||
@@ -33,6 +32,8 @@
|
||||
-keepattributes SourceFile,LineNumberTable,Signature,*Annotation*
|
||||
|
||||
-keep class org.spongycastle.** {*;}
|
||||
-keep class org.bouncycastle.** {*;}
|
||||
|
||||
# SSHd requires mina, and mina uses reflection so some classes would get deleted
|
||||
-keep class org.apache.mina.** {*;}
|
||||
|
||||
-keep class org.kde.kdeconnect.** {*;}
|
||||
|
@@ -26,7 +26,6 @@
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/device_not_paired"
|
||||
android:id="@+id/pair_message"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@@ -20,6 +20,11 @@
|
||||
android:id="@+id/no_players"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/artImageView" />
|
||||
|
||||
<Spinner
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -151,7 +156,6 @@
|
||||
android:layout_marginEnd="10dip"
|
||||
android:id="@+id/imageView"
|
||||
android:layout_weight="0"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:contentDescription="@string/mpris_volume"
|
||||
android:src="@drawable/ic_volume"
|
||||
/>
|
||||
|
@@ -6,14 +6,12 @@
|
||||
android:orientation="vertical"
|
||||
android:gravity="bottom">
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="KDE Connect"
|
||||
android:textColor="#FFF"
|
||||
android:textStyle="bold"
|
||||
android:layout_above="@+id/device_name"
|
||||
android:id="@+id/kdeconnect_label"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingStart="16dp"
|
||||
@@ -29,9 +27,6 @@
|
||||
android:id="@+id/device_name"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:textColor="#fff"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingRight="48dp"
|
||||
|
@@ -18,14 +18,13 @@
|
||||
Preference is able to place a specific widget for its particular
|
||||
type in the "widget_frame" layout. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:res="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingEnd="?android:attr/scrollbarSize"
|
||||
android:paddingRight="?android:attr/scrollbarSize"
|
||||
android:background="?attr/selectableItemBackground" >
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@+android:id/widget_frame"
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Controles remotos multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla l\'audiu/videu dende\'l to teléfonu</string>
|
||||
<string name="pref_plugin_runcommand">Executar comandu</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa un comandu nel to sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Unvia y recibe pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronización d\'avisos</string>
|
||||
@@ -151,7 +150,6 @@
|
||||
<string name="pref_plugin_telepathy">Unviar SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Unvia mensaxes de testu dende\'l to escritoriu</string>
|
||||
<string name="plugin_not_supported">Esti complementu nun ta sofitáu pol preséu</string>
|
||||
<string name="findmyphone_title">Alcuéntra\'l mio teléfonu</string>
|
||||
<string name="findmyphone_description">Fai sonar esti teléfonu pa que pueas alcontralu.</string>
|
||||
<string name="findmyphone_found">Alcontrar</string>
|
||||
<string name="open">Abrir</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controls multimèdia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla l\'àudio i el vídeo del vostre telèfon</string>
|
||||
<string name="pref_plugin_runcommand">Executa una ordre</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa una ordre al vostre sistema</string>
|
||||
<string name="pref_plugin_runcommand_desc">Activa les ordres remotes des del vostre telèfon</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envia i rep els pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronitza les notificacions</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Cancel·lat per l\'usuari</string>
|
||||
<string name="error_canceled_by_other_peer">Cancel·lat per l\'altre parell</string>
|
||||
<string name="error_invalid_key">S\'ha rebut una clau no vàlida</string>
|
||||
<string name="encryption_info_title">Informació d\'encriptatge</string>
|
||||
<string name="encryption_info_msg_no_ssl">L\'altre dispositiu no usa una versió recent del KDE Connect, s\'utilitzarà el mètode d\'encriptatge antic.</string>
|
||||
<string name="my_device_fingerprint">L\'empremta digital SHA1 del certificat del vostre dispositiu és:</string>
|
||||
<string name="remote_device_fingerprint">L\'empremta digital SHA1 del certificat del dispositiu remot és:</string>
|
||||
<string name="pair_requested">S\'ha demanat aparellar</string>
|
||||
<string name="pairing_request_from">S\'ha demanat aparellar des de %1s</string>
|
||||
<string name="received_url_title">S\'ha rebut un vincle des de %1s</string>
|
||||
@@ -151,7 +155,7 @@
|
||||
<string name="pref_plugin_telepathy">Envia un SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Envia missatges de text des de l\'escriptori</string>
|
||||
<string name="plugin_not_supported">Aquest connector no és admès pel dispositiu</string>
|
||||
<string name="findmyphone_title">Troba el meu telèfon</string>
|
||||
<string name="findmyphone_title">Cerca el meu telèfon</string>
|
||||
<string name="findmyphone_description">Fa sonar aquest telèfon perquè el pugueu trobar.</string>
|
||||
<string name="findmyphone_found">L\'he trobat</string>
|
||||
<string name="open">Obre</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Ovládání multimédií</string>
|
||||
<string name="pref_plugin_mpris_desc">Ovládejte audio/video z vašeho telefonu</string>
|
||||
<string name="pref_plugin_runcommand">Spustit příkaz</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustí příkaz na vašem počítači</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Posílat a přijímat ping</string>
|
||||
<string name="pref_plugin_notifications">Synchronizace upozornění</string>
|
||||
@@ -66,6 +65,7 @@
|
||||
<string name="error_canceled_by_user">Přerušeno uživatelem</string>
|
||||
<string name="error_canceled_by_other_peer">Přerušeno druhým uživatelem</string>
|
||||
<string name="error_invalid_key">Byl přijat neplatný klíč</string>
|
||||
<string name="encryption_info_title">Informace o šifrování</string>
|
||||
<string name="pair_requested">Bylo vyžádáno párování</string>
|
||||
<string name="pairing_request_from">Požadavek o párování z %1s</string>
|
||||
<string name="received_url_title">Přijat odkaz od %1s</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimediekontroller</string>
|
||||
<string name="pref_plugin_mpris_desc">Styr lyd og video fra din telefon</string>
|
||||
<string name="pref_plugin_runcommand">Kør kommando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Kører en kommando på dit system</string>
|
||||
<string name="pref_plugin_runcommand_desc">Kør eksterne kommandoer fra din telefon</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send og modtag ping</string>
|
||||
<string name="pref_plugin_notifications">Synk. af bekendtgørelser</string>
|
||||
@@ -32,6 +32,8 @@
|
||||
<string name="mousepad_info">Bevæg en finger på skærmen for at flytte musemarkøren. Tap for at klikke og brug to/tre-fingre for højre og midterste museknap. Brug et langt tryk til at trække og slippe.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Angiv handling for tap med to fingre</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Angiv handling for tap med tre fingre</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Angiv følsomhed for touchpad</string>
|
||||
<string name="mousepad_scroll_direction_title">Omvendt rulleretning</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Højreklik</item>
|
||||
<item>Midterklik</item>
|
||||
@@ -39,12 +41,13 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">højre</string>
|
||||
<string name="mousepad_triple_default">midter</string>
|
||||
<string name="mousepad_sensitivity_default">standard</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
<item>Mest langsom</item>
|
||||
<item>Over mest langsom</item>
|
||||
<item>Standard</item>
|
||||
<item>Over standard</item>
|
||||
<item>Hurtigst</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Forbundne enheder</string>
|
||||
<string name="category_not_paired_devices">Tilgængelig enheder</string>
|
||||
@@ -63,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Annulleret af brugeren</string>
|
||||
<string name="error_canceled_by_other_peer">Annulleret af modpart</string>
|
||||
<string name="error_invalid_key">Ugyldige nøgle modtaget</string>
|
||||
<string name="encryption_info_title">Krypteringsinfo</string>
|
||||
<string name="encryption_info_msg_no_ssl">Den anden enhed bruger ikke en nylig version af KDE Connect, og bruger dermed den forældede krypteringsmetode.</string>
|
||||
<string name="my_device_fingerprint">SHA1-fingeraftrykket for dit enhedscertifikat er:</string>
|
||||
<string name="remote_device_fingerprint">SHA1-fingeraftrykket for det eksterne enhedscertifikat er:</string>
|
||||
<string name="pair_requested">Anmodet om parring</string>
|
||||
<string name="pairing_request_from">Parringsanmodning fra %1s</string>
|
||||
<string name="received_url_title">Modtog link fra %1s</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimedia-Bedienung</string>
|
||||
<string name="pref_plugin_mpris_desc">Audio und Video mit Ihrem Telefon steuern</string>
|
||||
<string name="pref_plugin_runcommand">Befehl ausführen</string>
|
||||
<string name="pref_plugin_runcommand_desc">Führt einen Befehl auf Ihrem System aus</string>
|
||||
<string name="pref_plugin_runcommand_desc">Von Ihrem Telefon Befehle auf anderen Geräten ausführen</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Senden und Empfangen von Pings</string>
|
||||
<string name="pref_plugin_notifications">Benachrichtigungs-Abgleich</string>
|
||||
@@ -29,6 +29,9 @@
|
||||
<string name="send_ping">Ping senden</string>
|
||||
<string name="open_mpris_controls">Multimedia-Bedienung</string>
|
||||
<string name="open_mousepad">Ferneingabe</string>
|
||||
<string name="mousepad_double_tap_settings_title">Aktionsausführung bei Berührung mit zwei Fingern einstellen</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Aktionsausführung bei Berührung mit drei Fingern einstellen</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Empfindlichkeit des Touchpads einstellen</string>
|
||||
<string name="mousepad_scroll_direction_title">Bildlaufrichtung umkehren</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Rechtsklick</item>
|
||||
@@ -62,6 +65,9 @@
|
||||
<string name="error_canceled_by_user">Abbruch durch Benutzer</string>
|
||||
<string name="error_canceled_by_other_peer">Abbruch durch Gegenstelle</string>
|
||||
<string name="error_invalid_key">Ungültiger Schlüssel empfangen</string>
|
||||
<string name="encryption_info_title">Verschlüsselungsinformationen</string>
|
||||
<string name="my_device_fingerprint">Der SHA1-Fingerabdruck Ihres Gerätezertifikats lautet:</string>
|
||||
<string name="remote_device_fingerprint">Der SHA1-Fingerabdruck des Gerätezertifikats der Gegenstelle lautet:</string>
|
||||
<string name="pair_requested">Verbindung angefordert</string>
|
||||
<string name="pairing_request_from">Verbindungsanfrage von %1s</string>
|
||||
<string name="received_url_title">Verknüpfung von %1s erhalten</string>
|
||||
@@ -95,6 +101,7 @@
|
||||
<string name="mpris_next">Weiter</string>
|
||||
<string name="mpris_volume">Lautstärke</string>
|
||||
<string name="mpris_settings">Multimedia-Einstellungen</string>
|
||||
<string name="mpris_time_settings_title">Knöpfe Vorwärts/Rückwärts</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 Sekunden</item>
|
||||
<item>20 Sekunden</item>
|
||||
@@ -141,7 +148,7 @@
|
||||
<string name="pref_plugin_telepathy">SMS senden</string>
|
||||
<string name="pref_plugin_telepathy_desc">Text-Nachrichten von Ihrer Arbeitsfläche senden</string>
|
||||
<string name="plugin_not_supported">Dieses Modul wird durch das Gerät nicht unterstützt</string>
|
||||
<string name="findmyphone_title">Telefon suchen</string>
|
||||
<string name="findmyphone_title">Mein Telefon suchen</string>
|
||||
<string name="findmyphone_description">Ruft dieses Telefon an, um es zu suchen.</string>
|
||||
<string name="findmyphone_found">Gefunden</string>
|
||||
<string name="open">Öffnen</string>
|
||||
|
163
res/values-el/strings.xml
Normal file
163
res/values-el/strings.xml
Normal file
@@ -0,0 +1,163 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="pref_plugin_telephony">Ειδοποιήσεις τηλεφωνίας</string>
|
||||
<string name="pref_plugin_telephony_desc">Αποστολή ειδοποιήσεων για SMS και κλήσεις</string>
|
||||
<string name="pref_plugin_battery">Αναφορά μπαταρίας</string>
|
||||
<string name="pref_plugin_battery_desc">Περιοδική αναφορά κατάστασης μπαταρίας</string>
|
||||
<string name="pref_plugin_sftp">Αποκάλυψη συστήματος αρχείων</string>
|
||||
<string name="pref_plugin_sftp_desc">Επιτρέπει την απομακρυσμένη περιήγηση του συστήματος αρχείων του κινητού</string>
|
||||
<string name="pref_plugin_clipboard">Συγχρονισμός προχείρου</string>
|
||||
<string name="pref_plugin_clipboard_desc">Διαμοιρασμός περιεχομένου προχείρου</string>
|
||||
<string name="pref_plugin_mousepad">Απομακρυσμένη είσοδος στοιχείων</string>
|
||||
<string name="pref_plugin_mousepad_desc">Χρήση του κινητού ως ποντίκι και πληκτρολόγιο</string>
|
||||
<string name="pref_plugin_mpris">Κονσόλα πολυμέσων</string>
|
||||
<string name="pref_plugin_mpris_desc">Έλεγχος μουσικής/βίντεο από το κινητό</string>
|
||||
<string name="pref_plugin_runcommand">Εκτέλεση εντολής</string>
|
||||
<string name="pref_plugin_runcommand_desc">Εκτέλεση απομακρυσμένων εντολών από το κινητό</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Αποστολή και λήψη pings</string>
|
||||
<string name="pref_plugin_notifications">Συγχρονισμός ειδοποιήσεων</string>
|
||||
<string name="pref_plugin_notifications_desc">Πρόσβαση σε ειδοποιήσεις από άλλες συσκευές</string>
|
||||
<string name="pref_plugin_sharereceiver">Διαμοιρασμός και λήψη</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Διαμοιρασμός αρχείων και URL μεταξύ συσκευών</string>
|
||||
<string name="plugin_not_available">Αυτή η λειτουργία δεν είναι διαθέσιμη στην τρέχουσα έκδοση του Android</string>
|
||||
<string name="device_list_empty">Χωρίς συσκευές</string>
|
||||
<string name="ok">Εντάξει</string>
|
||||
<string name="cancel">Ακύρωση</string>
|
||||
<string name="open_settings">Ρυθμίσεις ανοίγματος</string>
|
||||
<string name="no_permissions">Απαιτείται παραχώρηση δικαιωμάτων για την πρόσβαση σε ειδοποιήσεις</string>
|
||||
<string name="send_ping">Αποστολή ping</string>
|
||||
<string name="open_mpris_controls">Έλεγχος πολυμέσων</string>
|
||||
<string name="open_mousepad">Απομακρυσμένη είσοδος στοιχείων</string>
|
||||
<string name="mousepad_info">Μετακινείστε το δάκτυλο στην οθόνη για να μετακινηθεί ο δρομέας του ποντικιού. Χτυπήστε για κλικ και χρησιμοποιήστε δύο/τρία δάκτυλα για δεξί και μεσαίο κλικ. Πιέστε με διάρκεια για μετακίνηση και απόθεση.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Ρύθμιση δύο δακτύλων για την ενέργεια χτυπήματος</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Ρύθμιση τριών δακτύλων για την ενέργεια χτυπήματος</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Ρύθμιση ευαισθησίας της οθόνης αφής</string>
|
||||
<string name="mousepad_scroll_direction_title">Κατεύθυνση ανάστροφης κύλησης</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Δεξί κλικ</item>
|
||||
<item>Μεσαίο κλικ</item>
|
||||
<item>Τίποτα</item>
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">δεξί</string>
|
||||
<string name="mousepad_triple_default">μεσαίο</string>
|
||||
<string name="mousepad_sensitivity_default">προκαθορισμένο</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Το πιο αργό</item>
|
||||
<item>Πάνω από το πιο αργό</item>
|
||||
<item>Προκαθορισμένο</item>
|
||||
<item>Πάνω από το προκαθορισμένο</item>
|
||||
<item>Το ταχύτερο</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Συνδεδεμένες συσκευές</string>
|
||||
<string name="category_not_paired_devices">Διαθέσιμες συσκευές</string>
|
||||
<string name="category_remembered_devices">Συσκευές στη μνήμη</string>
|
||||
<string name="plugins_failed_to_load">Αποτυχία φόρτωσης προσθέτων (χτυπήστε για περισσότερες πληροφορίες):</string>
|
||||
<string name="device_menu_plugins">Ρυθμίσεις προσθέτων</string>
|
||||
<string name="device_menu_unpair">Διαχωρισμός</string>
|
||||
<string name="device_not_reachable">Η συζευγμένη συσκευή δεν είναι προσβάσιμη</string>
|
||||
<string name="pair_new_device">Σύζευξη νέας συσκευής</string>
|
||||
<string name="unknown_device">Άγνωστη συσκευή</string>
|
||||
<string name="error_not_reachable">Η συσκευή δεν είναι προσβάσιμη</string>
|
||||
<string name="error_already_requested">Η σύζευξη ήδη ζητήθηκε</string>
|
||||
<string name="error_already_paired">Η συσκευή ήδη συζεύχθηκε</string>
|
||||
<string name="error_could_not_send_package">Αδυναμία αποστολής πακέτου</string>
|
||||
<string name="error_timed_out">Τέλος χρονικού ορίου</string>
|
||||
<string name="error_canceled_by_user">Ακυρώθηκε από τον χρήστη</string>
|
||||
<string name="error_canceled_by_other_peer">Ακυρώθηκε από άλλον χρήστη</string>
|
||||
<string name="error_invalid_key">Ελήφθη μη έγκυρο κλειδί</string>
|
||||
<string name="encryption_info_title">Πληροφορίες κρυπτογράφησης</string>
|
||||
<string name="encryption_info_msg_no_ssl">Η άλλη συσκευή δεν χρησιμοποιεί μια πρόσφατη έκδοση του KDE Connect, θα χρησιμοποιηθεί η παλαιά μέθοδος κρυπτογράφησης.</string>
|
||||
<string name="my_device_fingerprint">Το ίχνος SHA1 του πιστοποιητικού της συσκευής σας είναι:</string>
|
||||
<string name="remote_device_fingerprint">Το ίχνος SHA1 του πιστοποιητικού της απομακρυσμένης συσκευής είναι:</string>
|
||||
<string name="pair_requested">Ζητήθηκε σύζευξη</string>
|
||||
<string name="pairing_request_from">Αίτημα σύζευξης από %1s</string>
|
||||
<string name="received_url_title">Ελήφθη σύνδεσμος από %1s</string>
|
||||
<string name="received_url_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="incoming_file_title">Εισερχόμενο αρχείο από %1s</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Αποστολή αρχείου σε %1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="received_file_title">Ελήφθη αρχείο από %1s</string>
|
||||
<string name="received_file_fail_title">Αποτυχία λήψης αρχείου από %1s</string>
|
||||
<string name="received_file_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="sent_file_title">Εστάλη αρχείο στο %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Αποτυχία αποστολής αρχείου %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Χτυπήστε για να απαντήσετε</string>
|
||||
<string name="reconnect">Επανασύνδεση</string>
|
||||
<string name="right_click">Αποστολή δεξιού κλικ</string>
|
||||
<string name="middle_click">Αποστολή μεσαίου κλικ</string>
|
||||
<string name="show_keyboard">Εμφάνιση πληκτρολογίου</string>
|
||||
<string name="device_not_paired">Η συσκευή δεν συζεύχθηκε</string>
|
||||
<string name="request_pairing">Αίτημα σύζευξης</string>
|
||||
<string name="pairing_accept">Αποδοχή</string>
|
||||
<string name="pairing_reject">Απόρριψη</string>
|
||||
<string name="device">Συσκευή</string>
|
||||
<string name="pair_device">Σύζευξη συσκευής</string>
|
||||
<string name="remote_control">Απομακρυσμένος έλεγχος</string>
|
||||
<string name="settings">Ρυθμίσεις KDE Connect</string>
|
||||
<string name="mpris_play">Αναπαραγωγή</string>
|
||||
<string name="mpris_previous">Προηγούμενο</string>
|
||||
<string name="mpris_rew">Ταχεία ώθηση όπισθεν</string>
|
||||
<string name="mpris_ff">Ταχεία προώθηση</string>
|
||||
<string name="mpris_next">Επόμενο</string>
|
||||
<string name="mpris_volume">Τόμος</string>
|
||||
<string name="mpris_settings">Ρυθμίσεις πολυμέσων</string>
|
||||
<string name="mpris_time_settings_title">Κουμπιά ταχείας ώθησης</string>
|
||||
<string name="mpris_time_settings_summary">Χρονική προσαρμογή ταχείας ώθησης ανάλογα με την πίεση.</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 seconds</item>
|
||||
<item>20 seconds</item>
|
||||
<item>30 seconds</item>
|
||||
<item>1 λεπτό</item>
|
||||
<item>2 λεπτά</item>
|
||||
</string-array>
|
||||
<string name="share_to">Διαμοιρασμός με...</string>
|
||||
<string name="protocol_version_older">Η συσκευή αυτή χρησιμοποιεί παλαιά έκδοση πρωτοκόλλου</string>
|
||||
<string name="protocol_version_newer">Η συσκευή αυτή χρησιμοποιεί νεότερη έκδοση πρωτοκόλλου</string>
|
||||
<string name="general_settings">Γενικές ρυθμίσεις</string>
|
||||
<string name="plugin_settings">Ρυθμίσεις</string>
|
||||
<string name="plugin_settings_with_name">%s ρυθμίσεις</string>
|
||||
<string name="device_name">Όνομα συσκευής</string>
|
||||
<string name="device_name_preference_summary">%s</string>
|
||||
<string name="invalid_device_name">Μη έγκυρο όνομα συσκευής</string>
|
||||
<string name="shareplugin_text_saved">Ελήφθη κείμενο, αποθηκεύτηκε στο πρόχειρο</string>
|
||||
<string name="custom_devices_settings">Προσαρμοσμένη λίστα συσκευών</string>
|
||||
<string name="pair_device_action">Σύζευξη νέας συσκευής</string>
|
||||
<string name="unpair_device_action">Διαχωρισμός %s</string>
|
||||
<string name="custom_device_list">Προσθήκη συσκευών ανά IP</string>
|
||||
<string name="share_notification_preference">Θορυβώδεις ειδοποιήσεις</string>
|
||||
<string name="share_notification_preference_summary">Δόνηση και ηχητική ένδειξη με τη λήψη αρχείου</string>
|
||||
<string name="title_activity_notification_filter">Φιλτράρισμα ειδοποιήσεων</string>
|
||||
<string name="filter_apps_info">Οι ειδοποιήσεις θα συγχρονίζονται για επιλεγμένες εφαρμογές.</string>
|
||||
<string name="sftp_internal_storage">Εσωτερικός αποθηκευτικός χώρος</string>
|
||||
<string name="sftp_all_files">Όλα τα αρχεία</string>
|
||||
<string name="sftp_sdcard_num">SD card %d</string>
|
||||
<string name="sftp_sdcard">SD card</string>
|
||||
<string name="sftp_readonly">(ανάγνωση μόνο)</string>
|
||||
<string name="sftp_camera">Φωτογραφίες</string>
|
||||
<string name="add_host">Προσθήκη υπολογιστή/IP</string>
|
||||
<string name="add_host_hint">Όνομα υπολογιστή ή IP</string>
|
||||
<string name="no_players_connected">Δεν βρέθηκαν συσκευές αναπαραγωγής</string>
|
||||
<string name="custom_dev_list_help">Χρησιμοποιήστε την επιλογή αυτή μόνο αν η συσκευή σας δεν έχει εντοπιστεί αυτόματα. Δώστε την IP διεύθυνση ή το όνομα του υπολογιστή και αγγίξτε το κουμπί για να προστεθεί στη λίστα. Αγγίξτε ένα αντικείμενο της λίστας για να το αφαιρέσετε.</string>
|
||||
<string name="mpris_player_on_device">%1$s σε %2$s</string>
|
||||
<string name="send_files">Αποστολή αρχείων</string>
|
||||
<string name="pairing_title">Συσκευές KDE Connect</string>
|
||||
<string name="pairing_description">Άλλες συσκευές με KDE Connect στο ίδιο δίκτυο θα εμφανίζονται εδώ.</string>
|
||||
<string name="device_paired">Η συσκευή συζεύχθηκε</string>
|
||||
<string name="device_rename_title">Μετονομασία συσκευής</string>
|
||||
<string name="device_rename_confirm">Μετονομασία</string>
|
||||
<string name="refresh">Ανανέωση</string>
|
||||
<string name="unreachable_description">Αυτή η συζευγμένη συσκευή δεν είναι προσβάσιμη. Βεβαιωθείτε ότι είναι συνδεδεμένη στο δίκτυό σας.</string>
|
||||
<string name="no_file_browser">Δεν υπάρχουν εγκατεστημένοι περιηγητές αρχείων.</string>
|
||||
<string name="pref_plugin_telepathy">Αποστολή SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Αποστολή μηνυμάτων κειμένου από τον υπολογιστή σας</string>
|
||||
<string name="plugin_not_supported">Αυτό το πρόσθετο δεν υποστηρίζεται από τη συσκευή</string>
|
||||
<string name="findmyphone_title">Αναζήτηση του κινητού μου</string>
|
||||
<string name="findmyphone_description">Καλεί αυτό το κινητό ώστε να το εντοπίσετε.</string>
|
||||
<string name="findmyphone_found">Βρέθηκε</string>
|
||||
<string name="open">Άνοιγμα</string>
|
||||
<string name="close">Κλείσιμο</string>
|
||||
</resources>
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Multimedia controls</string>
|
||||
<string name="pref_plugin_mpris_desc">Control audio/video from your phone</string>
|
||||
<string name="pref_plugin_runcommand">Run Command</string>
|
||||
<string name="pref_plugin_runcommand_desc">Runs a command on your system</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send and receive pings</string>
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
@@ -151,7 +150,6 @@
|
||||
<string name="pref_plugin_telepathy">Send SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string>
|
||||
<string name="plugin_not_supported">This plugin is not supported by the device</string>
|
||||
<string name="findmyphone_title">Find My Phone</string>
|
||||
<string name="findmyphone_description">Rings this phone so you can find it.</string>
|
||||
<string name="findmyphone_found">Found</string>
|
||||
<string name="open">Open</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controles multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controlar audio y vídeo desde el teléfono</string>
|
||||
<string name="pref_plugin_runcommand">Ejecutar orden</string>
|
||||
<string name="pref_plugin_runcommand_desc">Ejecuta una orden en su sistema</string>
|
||||
<string name="pref_plugin_runcommand_desc">Desencadenar órdenes remotas desde su teléfono</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Enviar y recibir pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronizar notificaciones</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Cancelado por el usuario</string>
|
||||
<string name="error_canceled_by_other_peer">Cancelado por la otra parte</string>
|
||||
<string name="error_invalid_key">Se ha recibido una clave no valida</string>
|
||||
<string name="encryption_info_title">Información de cifrado</string>
|
||||
<string name="encryption_info_msg_no_ssl">El otro dispositivo no dispone de una versión reciente de KDE Connect, se usará un método de cifrado antiguo.</string>
|
||||
<string name="my_device_fingerprint">La huella digital SHA1 del certificado de su dispositivo es:</string>
|
||||
<string name="remote_device_fingerprint">La huella digital SHA1 del certificado del dispositivo remoto es:</string>
|
||||
<string name="pair_requested">Vinculación solicitada</string>
|
||||
<string name="pairing_request_from">Solicitud de vinculación de %1s</string>
|
||||
<string name="received_url_title">Enlace recibido desde %1s</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Multimedian ohjaus</string>
|
||||
<string name="pref_plugin_mpris_desc">Ohjaa ääntä ja videota puhelimestasi</string>
|
||||
<string name="pref_plugin_runcommand">Suorita komento</string>
|
||||
<string name="pref_plugin_runcommand_desc">Suorita komento järjestelmässäsi</string>
|
||||
<string name="pref_plugin_ping">Tiedustelupaketti</string>
|
||||
<string name="pref_plugin_ping_desc">Lähetä ja vastaanota tiedustelupaketteja</string>
|
||||
<string name="pref_plugin_notifications">Ilmoitusten synkronointi</string>
|
||||
@@ -151,7 +150,6 @@
|
||||
<string name="pref_plugin_telepathy">Lähetä tekstiviesti</string>
|
||||
<string name="pref_plugin_telepathy_desc">Lähetä tekstiviestejä työpöydältäsi</string>
|
||||
<string name="plugin_not_supported">Laite ei tue tätä liitännäistä</string>
|
||||
<string name="findmyphone_title">Löydä puhelimeni</string>
|
||||
<string name="findmyphone_description">Laittaa puhelimen soimaan, jotta voit löytää sen.</string>
|
||||
<string name="findmyphone_found">Löytyi</string>
|
||||
<string name="open">Avaa</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Controis multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controle o son e o vídeo desde o teléfono.</string>
|
||||
<string name="pref_plugin_runcommand">Executar unha orde</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa unha orde no sistema.</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envíe e reciba pings.</string>
|
||||
<string name="pref_plugin_notifications">Sincronización de notificacións</string>
|
||||
@@ -151,7 +150,6 @@
|
||||
<string name="pref_plugin_telepathy">Enviar unha mensaxe de texto</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensaxes de texto desde un computador de escritorio.</string>
|
||||
<string name="plugin_not_supported">O dispositivo non é compatíbel con este complemento.</string>
|
||||
<string name="findmyphone_title">Atopar o móbil</string>
|
||||
<string name="findmyphone_description">Reproduce un son de chamada no móbil para que poida atopalo.</string>
|
||||
<string name="findmyphone_found">Atopado</string>
|
||||
<string name="open">Abrir</string>
|
||||
|
163
res/values-he/strings.xml
Normal file
163
res/values-he/strings.xml
Normal file
@@ -0,0 +1,163 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="pref_plugin_telephony">התראות טלפוניות</string>
|
||||
<string name="pref_plugin_telephony_desc">שלח התראה על הודעות ושיחות</string>
|
||||
<string name="pref_plugin_battery">דיווח סוללה</string>
|
||||
<string name="pref_plugin_battery_desc">מדווח על אחוז הסוללה למחשב</string>
|
||||
<string name="pref_plugin_sftp">גישה לקבצים</string>
|
||||
<string name="pref_plugin_sftp_desc">אפשר להתקן המרוחק לדפדף בקבצים שבפלאפון</string>
|
||||
<string name="pref_plugin_clipboard">סנכנרון לוח העתקה</string>
|
||||
<string name="pref_plugin_clipboard_desc">שתף בין המחשבים את מה שמועתק</string>
|
||||
<string name="pref_plugin_mousepad">שליטה מרחוק</string>
|
||||
<string name="pref_plugin_mousepad_desc">השתמש בפלאפון כדי לשלוט בעכבר ובמקדלת של ההתקן המרוחק</string>
|
||||
<string name="pref_plugin_mpris">שליטה במדיה</string>
|
||||
<string name="pref_plugin_mpris_desc">שלוט בניגון שמע או וידאו מהפלאפון</string>
|
||||
<string name="pref_plugin_runcommand">הרץ פקודה</string>
|
||||
<string name="pref_plugin_runcommand_desc">הרץ פקודה במחשב מהפלאפון</string>
|
||||
<string name="pref_plugin_ping">פינג</string>
|
||||
<string name="pref_plugin_ping_desc">שלח וקבל פינגים</string>
|
||||
<string name="pref_plugin_notifications">סנכרון התראות</string>
|
||||
<string name="pref_plugin_notifications_desc">הראה את ההתראות מהפלאפון בהתקן אחר</string>
|
||||
<string name="pref_plugin_sharereceiver">שתף וקבל קבצים וכתובות</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">שתף וקבל קבצים וכתובת אינטרנט בין התקנים</string>
|
||||
<string name="plugin_not_available">אפשרות זו אינה זמינה בגרסת האנדרואיד שלך</string>
|
||||
<string name="device_list_empty">אין התקנים</string>
|
||||
<string name="ok">בסדר</string>
|
||||
<string name="cancel">בטל</string>
|
||||
<string name="open_settings">פתח הגדרות</string>
|
||||
<string name="no_permissions">אתה צריך לתת הרשאות לגישה להתראות</string>
|
||||
<string name="send_ping">שלח פינג</string>
|
||||
<string name="open_mpris_controls">שליטה על המדיה</string>
|
||||
<string name="open_mousepad">שלוט על המחשב</string>
|
||||
<string name="mousepad_info">הזז את האצבע על המסך כדי להזיז את סמן העכבר במחשב. לחץ כדי ללחוץ במחשב, השתמש בשנים או שלוש אצבעות כדי ללחוץ על המקש הימני או האמצעי. השתמש בליחצה ארוכה לגרירה ושחרור.</string>
|
||||
<string name="mousepad_double_tap_settings_title">הגדר פעולה ללחיצת שתי אצבעות</string>
|
||||
<string name="mousepad_triple_tap_settings_title">הגדר פעולה ללחיצת שלוש אצבעות</string>
|
||||
<string name="mousepad_sensitivity_settings_title">הגדר רגישות משטח המגע</string>
|
||||
<string name="mousepad_scroll_direction_title">הפוך את כיוון הגלילה</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>"לחיצה ימנית "</item>
|
||||
<item>לחיצה אצמעית (גלגלת)</item>
|
||||
<item>שום דבר</item>
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">ימין</string>
|
||||
<string name="mousepad_triple_default">אמצע</string>
|
||||
<string name="mousepad_sensitivity_default">ברירת מחדל</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>הכי איטי</item>
|
||||
<item>יותר מההכי איטי</item>
|
||||
<item>ברירת החדל</item>
|
||||
<item>יותר מהברירת מחדל</item>
|
||||
<item>הכי מהיר</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">התקנים מחוברים</string>
|
||||
<string name="category_not_paired_devices">התקנים זמינים</string>
|
||||
<string name="category_remembered_devices">התקנים זכורים</string>
|
||||
<string name="plugins_failed_to_load">טעינת התוסף נכשלה (לחץ לעוד מידע):</string>
|
||||
<string name="device_menu_plugins">הגדרות תוספים</string>
|
||||
<string name="device_menu_unpair">בטל התאמה</string>
|
||||
<string name="device_not_reachable">ההתקן המותאם לא זמין</string>
|
||||
<string name="pair_new_device">התאם התקן חדש</string>
|
||||
<string name="unknown_device">התקן לא ידוע</string>
|
||||
<string name="error_not_reachable">ההתקן לא זמין</string>
|
||||
<string name="error_already_requested">כבר בוקשה התאמה</string>
|
||||
<string name="error_already_paired">ההתקן כבר מותאם</string>
|
||||
<string name="error_could_not_send_package">לא יכול לשלוח חבילה</string>
|
||||
<string name="error_timed_out">נגמר הזמן</string>
|
||||
<string name="error_canceled_by_user">בוטל ע\"י המשתמש</string>
|
||||
<string name="error_canceled_by_other_peer">בוטל ע\"י מישהו אחר</string>
|
||||
<string name="error_invalid_key">התקבל מפתח לא חוקי</string>
|
||||
<string name="encryption_info_title">פרטי הצפנה</string>
|
||||
<string name="encryption_info_msg_no_ssl">ההתקן השני אינו משתמש בגרסה האחרונה של KDE Connect, משתמש בשיטת ההצפנה הישנה.</string>
|
||||
<string name="my_device_fingerprint">טביעת האצבע SHA1 של ההתקן היא:</string>
|
||||
<string name="remote_device_fingerprint">"טביעת האצבע SHA1 של ההתקן המרוחק היא: "</string>
|
||||
<string name="pair_requested">בקשת התאמה</string>
|
||||
<string name="pairing_request_from">בוקשה התאמה מ־%1s</string>
|
||||
<string name="received_url_title">התקבל קישור מ־%1s</string>
|
||||
<string name="received_url_text">לחץ כדי לפתוח את \'%1s\'</string>
|
||||
<string name="incoming_file_title">התקבל קובץ ־%1s</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">שולח קובץ ל־%1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="received_file_title">התקבל קובץ מ־%1s</string>
|
||||
<string name="received_file_fail_title">נכשל בקבלת קובץ מ־%1s</string>
|
||||
<string name="received_file_text">לחץ כדי לפתוח את %1s</string>
|
||||
<string name="sent_file_title">הקובץ נשלח ל־%1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">נכשל בשליחת הקובץ ל־%1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">לחץ כדי לענות</string>
|
||||
<string name="reconnect">התחבר מחדש</string>
|
||||
<string name="right_click">שלח לחיצה ימנית</string>
|
||||
<string name="middle_click">שלח לחיצה אמצעית (גלגלת)</string>
|
||||
<string name="show_keyboard">הראה מקלדת</string>
|
||||
<string name="device_not_paired">ההתקן לא מותאם</string>
|
||||
<string name="request_pairing">בקש התאמה</string>
|
||||
<string name="pairing_accept">אשר</string>
|
||||
<string name="pairing_reject">דחה</string>
|
||||
<string name="device">התקן</string>
|
||||
<string name="pair_device">התאם התקן</string>
|
||||
<string name="remote_control">שלוט מרחוק</string>
|
||||
<string name="settings">הגדרות KDE Connect</string>
|
||||
<string name="mpris_play">נגן</string>
|
||||
<string name="mpris_previous">הקודם</string>
|
||||
<string name="mpris_rew">דילוג אחורה</string>
|
||||
<string name="mpris_ff">דילוג קדימה</string>
|
||||
<string name="mpris_next">הבא</string>
|
||||
<string name="mpris_volume">עוצמה</string>
|
||||
<string name="mpris_settings">הגדרות מדיה</string>
|
||||
<string name="mpris_time_settings_title">כפתור דילוג קדימה או אחורה</string>
|
||||
<string name="mpris_time_settings_summary">בחר כמה זמן ידולג שלוחצים על דילוג קדימה או אחורה</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 שניות</item>
|
||||
<item>20 שניות</item>
|
||||
<item>חצי דקה</item>
|
||||
<item>דקה</item>
|
||||
<item>שתי דקות</item>
|
||||
</string-array>
|
||||
<string name="share_to">שתף ל...</string>
|
||||
<string name="protocol_version_older">ההתקן משתמש בגרסה ישנה יותר</string>
|
||||
<string name="protocol_version_newer">ההתקן משתמש בגרסה חדשה יותר</string>
|
||||
<string name="general_settings">הגדרות כלליות</string>
|
||||
<string name="plugin_settings">הגדרות</string>
|
||||
<string name="plugin_settings_with_name">הגדרות %s</string>
|
||||
<string name="device_name">שם המכשיר</string>
|
||||
<string name="device_name_preference_summary">%s</string>
|
||||
<string name="invalid_device_name">שם המכשיר לא תקין</string>
|
||||
<string name="shareplugin_text_saved">התקבל טקסט, נשמר ללוח העתקה</string>
|
||||
<string name="custom_devices_settings">רשימת התקנים מותאמת אישית</string>
|
||||
<string name="pair_device_action">התאם התקן חדש</string>
|
||||
<string name="unpair_device_action">בטל את ההתאמה עם %s</string>
|
||||
<string name="custom_device_list">הוסף התקן ע\"י כתובת IP</string>
|
||||
<string name="share_notification_preference">התראות רועשות</string>
|
||||
<string name="share_notification_preference_summary">רטוט ונגן צליל בעת קבלת קובץ</string>
|
||||
<string name="title_activity_notification_filter">סנן התראות</string>
|
||||
<string name="filter_apps_info">התראות יסונכרנו רק לאפליקציות נבחרות</string>
|
||||
<string name="sftp_internal_storage">זיכרון פנימי</string>
|
||||
<string name="sftp_all_files">כל הקבצים</string>
|
||||
<string name="sftp_sdcard_num">כרטיס זיכרון %d</string>
|
||||
<string name="sftp_sdcard">כרטיס זיכרון</string>
|
||||
<string name="sftp_readonly">(לקריאה בלבד)</string>
|
||||
<string name="sftp_camera">תמונות מצלמה</string>
|
||||
<string name="add_host">הוסף כתובת שרת או IP</string>
|
||||
<string name="add_host_hint">כתבות שרת או IP</string>
|
||||
<string name="no_players_connected">לא נמצא נגן</string>
|
||||
<string name="custom_dev_list_help">השתמש באפשרות זו רק אם המכשיר שלך לא מזוהה באופן אוטומטי. הקלד את כתובת הIP או את כינוי ההתקן למטה ולחץ על הכפתור כדי להוסיף לרשימה. לחץ על פריט קיים כדי להסיר אותו מהרשימה.</string>
|
||||
<string name="mpris_player_on_device">%1$s ב־%2$s</string>
|
||||
<string name="send_files">שלח קובץ</string>
|
||||
<string name="pairing_title">מכשירי KDE Connect</string>
|
||||
<string name="pairing_description">התקנים אחרים המריצים KDE Connect ברשת הנוכחית צריכים להופיע פה.</string>
|
||||
<string name="device_paired">התקן מתואם</string>
|
||||
<string name="device_rename_title">שנה שם התקן</string>
|
||||
<string name="device_rename_confirm">שנה שם</string>
|
||||
<string name="refresh">רענן</string>
|
||||
<string name="unreachable_description">ההתקן המתואם לא זמין, וודא שהוא מחובר לאותה רשת אליה התקן זה מחובר.</string>
|
||||
<string name="no_file_browser">לא נמצאו מנהלי קבצים מותקנים במכשיר זה.</string>
|
||||
<string name="pref_plugin_telepathy">שלח SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">שלח הודעות מהמחשב שלך</string>
|
||||
<string name="plugin_not_supported">התוסף הזה לא נתמך ע\"י המכשיר שלך</string>
|
||||
<string name="findmyphone_title">מצא את המכשיר שלי</string>
|
||||
<string name="findmyphone_description">הפעל רעש במכשיר כדי שתוכל למצוא אותו.</string>
|
||||
<string name="findmyphone_found">נמצא</string>
|
||||
<string name="open">פתח</string>
|
||||
<string name="close">סגור</string>
|
||||
</resources>
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controlli multimediali</string>
|
||||
<string name="pref_plugin_mpris_desc">Controlla la riproduzione audio/video dal telefono</string>
|
||||
<string name="pref_plugin_runcommand">Esegui comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Esegue un comando sul tuo sistema</string>
|
||||
<string name="pref_plugin_runcommand_desc">Esegui comandi remoti dal tuo telefono</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Invia e ricevi ping</string>
|
||||
<string name="pref_plugin_notifications">Sincronizzazione notifiche</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Annullata dall\'utente</string>
|
||||
<string name="error_canceled_by_other_peer">Annullata dal dispositivo remoto</string>
|
||||
<string name="error_invalid_key">Ricevuta chiave non valida</string>
|
||||
<string name="encryption_info_title">Informazioni di cifratura</string>
|
||||
<string name="encryption_info_msg_no_ssl">L\'altro dispositivo non utilizza una versione recente di KDE Connect, utilizzando il metodo di cifratura precedente.</string>
|
||||
<string name="my_device_fingerprint">L\'impronta digitale SHA1 del certificato di dispositivo è:</string>
|
||||
<string name="remote_device_fingerprint">L\'impronta digitale SHA1 del certificato di dispositivo remoto è:</string>
|
||||
<string name="pair_requested">Richiesta di associazione</string>
|
||||
<string name="pairing_request_from">Richiesta associazione da %1s</string>
|
||||
<string name="received_url_title">Collegamento ricevuto da %1s</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">멀티미디어 제어</string>
|
||||
<string name="pref_plugin_mpris_desc">휴대폰에서 오디오/비디오 제어</string>
|
||||
<string name="pref_plugin_runcommand">명령 실행</string>
|
||||
<string name="pref_plugin_runcommand_desc">시스템에서 명령 실행</string>
|
||||
<string name="pref_plugin_ping">핑</string>
|
||||
<string name="pref_plugin_ping_desc">핑 보내고 받기</string>
|
||||
<string name="pref_plugin_notifications">알림 동기화</string>
|
||||
@@ -147,7 +146,6 @@
|
||||
<string name="pref_plugin_telepathy">SMS 보내기</string>
|
||||
<string name="pref_plugin_telepathy_desc">데스크톱에서 문자 메시지 보내기</string>
|
||||
<string name="plugin_not_supported">장치에서 이 플러그인을 지원하지 않습니다</string>
|
||||
<string name="findmyphone_title">내 휴대폰 찾기</string>
|
||||
<string name="findmyphone_description">소리를 울려서 휴대폰을 찾는 데 도움을 줍니다.</string>
|
||||
<string name="findmyphone_found">찾았음</string>
|
||||
<string name="open">열기</string>
|
||||
|
@@ -101,7 +101,6 @@
|
||||
<string name="unreachable_description">Šis suporuotas įrenginys nepasiekiamas. Patikrinkite, ar jis prisijungęs prie to paties tinklo.</string>
|
||||
<string name="pref_plugin_telepathy">Siųsti SMS</string>
|
||||
<string name="plugin_not_supported">Telefonas nepalaiko šio papildinio</string>
|
||||
<string name="findmyphone_title">Rasti telefoną</string>
|
||||
<string name="findmyphone_description">Telefonas skambės, tad galėsite jį rasti.</string>
|
||||
<string name="findmyphone_found">Radau</string>
|
||||
<string name="open">Atverti</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Bediening van multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Bedien de audio/video vanaf uw telefoon</string>
|
||||
<string name="pref_plugin_runcommand">Commando uitvoeren</string>
|
||||
<string name="pref_plugin_runcommand_desc">Voert een commando uit op uw systeem</string>
|
||||
<string name="pref_plugin_runcommand_desc">Commando\'s afvuren op afstand vanaf uw telefoon</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Pings verzenden en ontvangen</string>
|
||||
<string name="pref_plugin_notifications">Synchronisatie van meldingen</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Geannuleerd door gebruiker</string>
|
||||
<string name="error_canceled_by_other_peer">Geannuleerd door andere kant</string>
|
||||
<string name="error_invalid_key">Ongeldige sleutel ontvangen</string>
|
||||
<string name="encryption_info_title">Versleutelde informatie</string>
|
||||
<string name="encryption_info_msg_no_ssl">Het andere apparaat gebruikt geen recente versie van KDE Connect, de verouderde versleutelingsmethode zal worden gebruikt.</string>
|
||||
<string name="my_device_fingerprint">De SHA1 vingerafdruk van het certificaat van uw apparaat is:</string>
|
||||
<string name="remote_device_fingerprint">De SHA1 vingerafdruk van het certificaat van het apparaat op afstand is:</string>
|
||||
<string name="pair_requested">Paar gevraagd</string>
|
||||
<string name="pairing_request_from">Verzoek om een paar te maken van %1s</string>
|
||||
<string name="received_url_title">Ontvangen koppeling van %1s</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Sterowanie multimediami</string>
|
||||
<string name="pref_plugin_mpris_desc">Steruj dźwiękiem/obrazem ze swojego telefonu</string>
|
||||
<string name="pref_plugin_runcommand">Wykonaj polecenie</string>
|
||||
<string name="pref_plugin_runcommand_desc">Wykonuje polecenie na twoim systmie</string>
|
||||
<string name="pref_plugin_runcommand_desc">Wyzwalaj zdalne polecenia z twojego telefonu</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Wysyłaj i otrzymuj pingi</string>
|
||||
<string name="pref_plugin_notifications">Powiadomienia synchronizacji</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Anulowane przez użytkownika</string>
|
||||
<string name="error_canceled_by_other_peer">Anulowane przez innego partnera</string>
|
||||
<string name="error_invalid_key">Otrzymano nieprawidłowy klucz</string>
|
||||
<string name="encryption_info_title">Zaszyfrowane informacje</string>
|
||||
<string name="encryption_info_msg_no_ssl">Drugie urządzenie nie używa ostatniej wersji KDE Connect, użyto przestarzałego szyfrowania.</string>
|
||||
<string name="my_device_fingerprint">Odcisk palca SHA1 certyfikatu twojego urządzenia to:</string>
|
||||
<string name="remote_device_fingerprint">Odcisk palca SHA1 certyfikatu twojego zdalnego urządzenia to:</string>
|
||||
<string name="pair_requested">Zażądano parowania</string>
|
||||
<string name="pairing_request_from">Żądanie parowania z %1s</string>
|
||||
<string name="received_url_title">Odebrano odsyłacz od %1s</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Controle multimídia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla áudio e vídeo a partir do seu telefone</string>
|
||||
<string name="pref_plugin_runcommand">Executar comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa um comando no seu sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envia e recebe pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronização de notificações</string>
|
||||
@@ -151,7 +150,6 @@
|
||||
<string name="pref_plugin_telepathy">Enviar SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensagens de texto do seu Desktop</string>
|
||||
<string name="plugin_not_supported">Este plugin não é suportado pelo dispositivo</string>
|
||||
<string name="findmyphone_title">Encontrar meu telefone</string>
|
||||
<string name="findmyphone_description">Faça este telefone tocar para encontrá-lo.</string>
|
||||
<string name="findmyphone_found">Encontrado</string>
|
||||
<string name="open">Abrir</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Comandos multimédia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controlar o áudio/vídeo a partir do seu telefone</string>
|
||||
<string name="pref_plugin_runcommand">Executar um Comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa um comando no seu sistema</string>
|
||||
<string name="pref_plugin_runcommand_desc">Despoletar comandos remotos a partir do seu telefone</string>
|
||||
<string name="pref_plugin_ping">Contacto</string>
|
||||
<string name="pref_plugin_ping_desc">Enviar e receber pedidos de contacto (\'ping\')</string>
|
||||
<string name="pref_plugin_notifications">Sincronização da notificação</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Cancelado pelo utilizador</string>
|
||||
<string name="error_canceled_by_other_peer">Cancelado pela outra máquina</string>
|
||||
<string name="error_invalid_key">Chave inválida recebida</string>
|
||||
<string name="encryption_info_title">Dados de Encriptação</string>
|
||||
<string name="encryption_info_msg_no_ssl">O outro dispositivo não usa uma versão recente do KDE Connect; será usado o método antigo de encriptação.</string>
|
||||
<string name="my_device_fingerprint">A impressão digital SHA1 do certificado do seu dispositivo é:</string>
|
||||
<string name="remote_device_fingerprint">A impressão digital SHA1 do certificado do dispositivo remoto é:</string>
|
||||
<string name="pair_requested">Emparelhamento pedido</string>
|
||||
<string name="pairing_request_from">Pedido de emparelhamento de %1s</string>
|
||||
<string name="received_url_title">Ligação recebida de %1s</string>
|
||||
@@ -151,7 +155,7 @@
|
||||
<string name="pref_plugin_telepathy">Enviar um SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensagens de texto a partir do seu ambiente de trabalho</string>
|
||||
<string name="plugin_not_supported">Este \'plugin\' não é suportado pelo dispositivo</string>
|
||||
<string name="findmyphone_title">Descobrir o Meu Telefone</string>
|
||||
<string name="findmyphone_title">Descobrir o meu telefone</string>
|
||||
<string name="findmyphone_description">Toca este telefone para que o possa encontrar.</string>
|
||||
<string name="findmyphone_found">Encontrado</string>
|
||||
<string name="open">Abrir</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">Управление воспроизведением</string>
|
||||
<string name="pref_plugin_mpris_desc">Управление медиапроигрывателем с телефона</string>
|
||||
<string name="pref_plugin_runcommand">Запуск команд</string>
|
||||
<string name="pref_plugin_runcommand_desc">Выполнение команд на вашем компьютере</string>
|
||||
<string name="pref_plugin_ping">Пинг</string>
|
||||
<string name="pref_plugin_ping_desc">Отправка и получение тестовых сигналов</string>
|
||||
<string name="pref_plugin_notifications">Синхронизация уведомлений</string>
|
||||
@@ -147,7 +146,6 @@
|
||||
<string name="pref_plugin_telepathy">Отправка SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Отправка SMS-сообщений с вашего компьютера</string>
|
||||
<string name="plugin_not_supported">Этот модуль не поддерживается устройством</string>
|
||||
<string name="findmyphone_title">Поиск телефона</string>
|
||||
<string name="findmyphone_description">Подача звукового сигнала на телефоне, чтобы вы могли его найти.</string>
|
||||
<string name="findmyphone_found">Найден</string>
|
||||
<string name="open">Открыть</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimediálne ovládače</string>
|
||||
<string name="pref_plugin_mpris_desc">Ovládať audio/video z vášho telefónu</string>
|
||||
<string name="pref_plugin_runcommand">Spustiť príkaz</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustí príkazy na vašom systéme</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustiť vzdialené príkazy z vášho telefónu</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Poslať a prijať pingy</string>
|
||||
<string name="pref_plugin_notifications">Synchronizácia pripomienok</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Zrušené používateľom</string>
|
||||
<string name="error_canceled_by_other_peer">Zrušené iným klientom</string>
|
||||
<string name="error_invalid_key">Získaný nesprávny kľúč</string>
|
||||
<string name="encryption_info_title">Informácia o šifrovaní</string>
|
||||
<string name="encryption_info_msg_no_ssl">Ďalšie zariadenie nepoužíva aktuálnu verziu KDE Connect, používam klasickú metódu šifrovania.</string>
|
||||
<string name="my_device_fingerprint">Odtlačok SHA1 vášho certifikátu zariadenia je:</string>
|
||||
<string name="remote_device_fingerprint">Odtlačok SHA1 vzdialeného certifikátu zariadenia je:</string>
|
||||
<string name="pair_requested">Spárovanie vyžiadané</string>
|
||||
<string name="pairing_request_from">Požiadavka na spárovanie od %1s</string>
|
||||
<string name="received_url_title">Prijatý odkaz od %1s</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimediakontroller</string>
|
||||
<string name="pref_plugin_mpris_desc">Styr ljud och video från telefonen</string>
|
||||
<string name="pref_plugin_runcommand">Kör kommando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Kör ett kommando på systemet</string>
|
||||
<string name="pref_plugin_runcommand_desc">Utlös fjärrkommandon från din telefon</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Skicka och ta emot ping</string>
|
||||
<string name="pref_plugin_notifications">Synkronisering av underrättelser</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Avbruten av användaren</string>
|
||||
<string name="error_canceled_by_other_peer">Avbruten av motparten</string>
|
||||
<string name="error_invalid_key">Ogiltig nyckel mottagen</string>
|
||||
<string name="encryption_info_title">Krypteringsinformation</string>
|
||||
<string name="encryption_info_msg_no_ssl">Den andra apparaten använder inte en aktuell version av KDE-anslut. Använder den föråldrade krypteringsmetoden.</string>
|
||||
<string name="my_device_fingerprint">SHA1-fingeravtryck för din apparats certifikat är:</string>
|
||||
<string name="remote_device_fingerprint">SHA1-fingeravtryck för den andra apparatens certifikat är:</string>
|
||||
<string name="pair_requested">Ihopparning begärd</string>
|
||||
<string name="pairing_request_from">Begäran om ihopparning från %1s</string>
|
||||
<string name="received_url_title">Tog emot länk från %1s</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Керування відтворенням</string>
|
||||
<string name="pref_plugin_mpris_desc">Керування відтворенням звуку та відео з вашого телефону</string>
|
||||
<string name="pref_plugin_runcommand">Виконати команду</string>
|
||||
<string name="pref_plugin_runcommand_desc">Виконує команду у вашій системі</string>
|
||||
<string name="pref_plugin_runcommand_desc">Віддалені команди з вашого телефону</string>
|
||||
<string name="pref_plugin_ping">Підтримання зв’язку</string>
|
||||
<string name="pref_plugin_ping_desc">Надсилання і отримання сигналів підтримання зв’язку</string>
|
||||
<string name="pref_plugin_notifications">Синхронізація сповіщень</string>
|
||||
@@ -66,6 +66,10 @@
|
||||
<string name="error_canceled_by_user">Скасовано користувачем</string>
|
||||
<string name="error_canceled_by_other_peer">Скасовано з іншого вузла пов’язування</string>
|
||||
<string name="error_invalid_key">Отримано некоректний ключ</string>
|
||||
<string name="encryption_info_title">Дані щодо шифрування</string>
|
||||
<string name="encryption_info_msg_no_ssl">На сторонньому пристрої не використовується нова версія KDE Connect, у якій використовується застарілий спосіб шифрування.</string>
|
||||
<string name="my_device_fingerprint">Відбиток SHA1 сертифіката пристрою:</string>
|
||||
<string name="remote_device_fingerprint">Відбиток SHA1 сертифіката віддаленого пристрою:</string>
|
||||
<string name="pair_requested">Запит щодо пов’язування</string>
|
||||
<string name="pairing_request_from">Запит щодо пов’язування від %1s</string>
|
||||
<string name="received_url_title">Отримано посилання від %1s</string>
|
||||
|
@@ -13,7 +13,6 @@
|
||||
<string name="pref_plugin_mpris">多媒体控制</string>
|
||||
<string name="pref_plugin_mpris_desc">从手机控制音频或视频</string>
|
||||
<string name="pref_plugin_runcommand">执行命令</string>
|
||||
<string name="pref_plugin_runcommand_desc">在您的系统上执行命令</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">发送和接受ping</string>
|
||||
<string name="pref_plugin_notifications">通知同步</string>
|
||||
@@ -148,7 +147,6 @@
|
||||
<string name="pref_plugin_telepathy">发送短消息</string>
|
||||
<string name="pref_plugin_telepathy_desc">从桌面发送短消息</string>
|
||||
<string name="plugin_not_supported">设备不支持此插件</string>
|
||||
<string name="findmyphone_title">找到我的手机</string>
|
||||
<string name="findmyphone_description">让手机响铃从而找到它</string>
|
||||
<string name="findmyphone_found">找到</string>
|
||||
<string name="open">打开</string>
|
||||
|
@@ -19,6 +19,8 @@
|
||||
<string name="pref_plugin_ping_desc">Send and receive pings</string>
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
<string name="pref_plugin_notifications_desc">Access your notifications from other devices</string>
|
||||
<string name="pref_plugin_receive_notifications">Receive notifications</string>
|
||||
<string name="pref_plugin_receive_notifications_desc">Receive notifications from other devices</string>
|
||||
<string name="pref_plugin_sharereceiver">Share and receive</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Share files and URLs between devices</string>
|
||||
<string name="plugin_not_available">This feature is not available in your Android version</string>
|
||||
@@ -85,8 +87,8 @@
|
||||
<string name="error_invalid_key">Invalid key received</string>
|
||||
<string name="encryption_info_title">Encryption Info</string>
|
||||
<string name="encryption_info_msg_no_ssl">The other device doesn\'t use a recent version of KDE Connect, using the legacy encryption method.</string>
|
||||
<string name="my_device_fingerprint">SHA1 fingerprint of your device certificate is : </string>
|
||||
<string name="remote_device_fingerprint">SHA1 fingerprint of remote device certificate is : </string>
|
||||
<string name="my_device_fingerprint">SHA1 fingerprint of your device certificate is:</string>
|
||||
<string name="remote_device_fingerprint">SHA1 fingerprint of remote device certificate is:</string>
|
||||
<string name="pair_requested">Pair requested</string>
|
||||
<string name="pairing_request_from">Pairing request from %1s</string>
|
||||
<string name="received_url_title">Received link from %1s</string>
|
||||
|
@@ -90,5 +90,6 @@ public abstract class BaseLink {
|
||||
|
||||
//TO OVERRIDE, should be sync
|
||||
public abstract void sendPackage(NetworkPackage np,Device.SendPackageStatusCallback callback);
|
||||
@Deprecated
|
||||
public abstract void sendPackageEncrypted(NetworkPackage np,Device.SendPackageStatusCallback callback, PublicKey key);
|
||||
}
|
||||
|
@@ -20,12 +20,8 @@
|
||||
|
||||
package org.kde.kdeconnect.Backends;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class BaseLinkProvider {
|
||||
|
@@ -21,73 +21,113 @@
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Helpers.StringsHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
public class LanLink extends BaseLink {
|
||||
|
||||
public interface LinkDisconnectedCallback {
|
||||
void linkDisconnected(LanLink brokenLink);
|
||||
}
|
||||
|
||||
public enum ConnectionStarted {
|
||||
Locally, Remotely;
|
||||
};
|
||||
|
||||
protected ConnectionStarted connectionSource; // If the other device sent me a broadcast,
|
||||
// I should not close the connection with it
|
||||
private ConnectionStarted connectionSource; // If the other device sent me a broadcast,
|
||||
// I should not close the connection with it
|
||||
// because it's probably trying to find me and
|
||||
// potentially ask for pairing.
|
||||
|
||||
private Channel channel = null;
|
||||
private boolean onSsl = false;
|
||||
private Socket socket = null;
|
||||
|
||||
private LinkDisconnectedCallback callback;
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
//Returns the old channel
|
||||
public Channel reset(Channel channel, ConnectionStarted connectionSource, boolean onSsl) {
|
||||
Channel oldChannel = this.channel;
|
||||
this.channel = channel;
|
||||
this.connectionSource = connectionSource;
|
||||
return oldChannel;
|
||||
}
|
||||
|
||||
public void closeSocket() {
|
||||
if (channel == null) {
|
||||
Log.e("KDE/LanLink", "Not yet connected");
|
||||
return;
|
||||
Log.i("LanLink/Disconnect","socket:"+ socket.hashCode());
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
channel.close();
|
||||
}
|
||||
|
||||
public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, Channel channel, ConnectionStarted connectionSource, boolean onSsl) {
|
||||
//Returns the old socket
|
||||
public Socket reset(final Socket newSocket, ConnectionStarted connectionSource) throws IOException {
|
||||
|
||||
Socket oldSocket = socket;
|
||||
socket = newSocket;
|
||||
|
||||
this.connectionSource = connectionSource;
|
||||
|
||||
if (oldSocket != null) {
|
||||
oldSocket.close(); //This should cancel the readThread
|
||||
}
|
||||
|
||||
//Log.e("LanLink", "Start listening");
|
||||
//Create a thread to take care of incoming data for the new socket
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(newSocket.getInputStream(), StringsHelper.UTF8));
|
||||
while (true) {
|
||||
String packet;
|
||||
try {
|
||||
packet = reader.readLine();
|
||||
} catch (SocketTimeoutException e) {
|
||||
continue;
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new IOException("End of stream");
|
||||
}
|
||||
if (packet.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
NetworkPackage np = NetworkPackage.unserialize(packet);
|
||||
receivedNetworkPackage(np);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i("LanLink", "Socket closed: " + newSocket.hashCode() + ". Reason: " + e.getMessage());
|
||||
try { Thread.sleep(300); } catch (InterruptedException ignored) {} // Wait a bit because we might receive a new socket meanwhile
|
||||
boolean thereIsaANewSocket = (newSocket != socket);
|
||||
if (!thereIsaANewSocket) {
|
||||
callback.linkDisconnected(LanLink.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
return oldSocket;
|
||||
}
|
||||
|
||||
public LanLink(Context context, String deviceId, LanLinkProvider linkProvider, Socket socket, ConnectionStarted connectionSource) throws IOException {
|
||||
super(context, deviceId, linkProvider);
|
||||
reset(channel, connectionSource, onSsl);
|
||||
callback = linkProvider;
|
||||
reset(socket, connectionSource);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,23 +141,9 @@ public class LanLink extends BaseLink {
|
||||
return new LanPairingHandler(device, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPackageReceiver(PackageReceiver pr) {
|
||||
super.addPackageReceiver(pr);
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(getDeviceId());
|
||||
if (device == null) return;
|
||||
if (!device.isPaired()) return;
|
||||
// If the device is already paired due to other link, just send a pairing request to get required attributes for this link
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Blocking, do not call from main thread
|
||||
private void sendPackageInternal(NetworkPackage np, final Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||
if (channel == null) {
|
||||
if (socket == null) {
|
||||
Log.e("KDE/sendPackage", "Not yet connected");
|
||||
callback.sendFailure(new NotYetConnectedException());
|
||||
return;
|
||||
@@ -128,7 +154,7 @@ public class LanLink extends BaseLink {
|
||||
//Prepare socket for the payload
|
||||
final ServerSocket server;
|
||||
if (np.hasPayload()) {
|
||||
server = openTcpSocketOnFreePort(context, getDeviceId(), onSsl);
|
||||
server = LanLinkProvider.openServerSocketOnFreePort(LanLinkProvider.PAYLOAD_TRANSFER_MIN_PORT);
|
||||
JSONObject payloadTransferInfo = new JSONObject();
|
||||
payloadTransferInfo.put("port", server.getLocalPort());
|
||||
np.setPayloadTransferInfo(payloadTransferInfo);
|
||||
@@ -141,49 +167,64 @@ public class LanLink extends BaseLink {
|
||||
np = RsaHelper.encrypt(np, key);
|
||||
}
|
||||
|
||||
//Log.e("LanLink/sendPackage", np.getType());
|
||||
|
||||
//Send body of the network package
|
||||
ChannelFuture future = channel.writeAndFlush(np.serialize()).sync();
|
||||
if (!future.isSuccess()) {
|
||||
Log.e("KDE/sendPackage", "!future.isWritten()");
|
||||
callback.sendFailure(future.cause());
|
||||
try {
|
||||
OutputStream writter = socket.getOutputStream();
|
||||
writter.write(np.serialize().getBytes(StringsHelper.UTF8));
|
||||
writter.flush();
|
||||
} catch (Exception e) {
|
||||
callback.sendFailure(e);
|
||||
e.printStackTrace();
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Send payload
|
||||
if (server != null) {
|
||||
OutputStream socket = null;
|
||||
Socket payloadSocket = null;
|
||||
OutputStream outputStream = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
//Wait a maximum of 10 seconds for the other end to establish a connection with our socket, close it afterwards
|
||||
server.setSoTimeout(10*1000);
|
||||
socket = server.accept().getOutputStream();
|
||||
|
||||
payloadSocket = server.accept();
|
||||
|
||||
//Convert to SSL if needed
|
||||
if (socket instanceof SSLSocket) {
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, false);
|
||||
}
|
||||
|
||||
outputStream = payloadSocket.getOutputStream();
|
||||
inputStream = np.getPayload();
|
||||
|
||||
Log.i("KDE/LanLink", "Beginning to send payload");
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
long progress = 0;
|
||||
InputStream stream = np.getPayload();
|
||||
while ((bytesRead = stream.read(buffer)) != -1) {
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
//Log.e("ok",""+bytesRead);
|
||||
progress += bytesRead;
|
||||
socket.write(buffer, 0, bytesRead);
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
if (np.getPayloadSize() > 0) {
|
||||
callback.sendProgress((int)(progress / np.getPayloadSize()));
|
||||
}
|
||||
}
|
||||
socket.flush();
|
||||
stream.close();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
Log.i("KDE/LanLink", "Finished sending payload ("+progress+" bytes written)");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/sendPackage", "Exception: "+e);
|
||||
callback.sendFailure(e);
|
||||
return;
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try { socket.close(); } catch (Exception e) { }
|
||||
}
|
||||
try { server.close(); } catch (Exception e) { }
|
||||
try { payloadSocket.close(); } catch (Exception e) { }
|
||||
try { inputStream.close(); } catch (Exception e) { }
|
||||
try { outputStream.close(); } catch (Exception e) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,14 +252,10 @@ public class LanLink extends BaseLink {
|
||||
//Blocking, do not call from main thread
|
||||
@Override
|
||||
public void sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||
if (onSsl) {
|
||||
sendPackageInternal(np, callback, null); // No need to encrypt
|
||||
}else {
|
||||
sendPackageInternal(np, callback, key);
|
||||
}
|
||||
sendPackageInternal(np, callback, key);
|
||||
}
|
||||
|
||||
public void injectNetworkPackage(NetworkPackage np) {
|
||||
private void receivedNetworkPackage(NetworkPackage np) {
|
||||
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
|
||||
try {
|
||||
@@ -227,27 +264,22 @@ public class LanLink extends BaseLink {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/onPackageReceived","Exception decrypting the package");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (np.hasPayloadTransferInfo()) {
|
||||
|
||||
Socket socket = null;
|
||||
Socket payloadSocket = new Socket();
|
||||
try {
|
||||
// Use ssl if existing link is on ssl
|
||||
if (onSsl) {
|
||||
SSLContext sslContext = SslHelper.getSslContext(context, getDeviceId(), true);
|
||||
socket = sslContext.getSocketFactory().createSocket();
|
||||
} else {
|
||||
socket = new Socket();
|
||||
if (socket instanceof SSLSocket) {
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true);
|
||||
}
|
||||
|
||||
int tcpPort = np.getPayloadTransferInfo().getInt("port");
|
||||
InetSocketAddress address = (InetSocketAddress)channel.remoteAddress();
|
||||
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
np.setPayload(socket.getInputStream(), np.getPayloadSize());
|
||||
InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||
payloadSocket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
np.setPayload(payloadSocket.getInputStream(), np.getPayloadSize());
|
||||
} catch (Exception e) {
|
||||
try { socket.close(); } catch(Exception ignored) { }
|
||||
try { payloadSocket.close(); } catch(Exception ignored) { }
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLink", "Exception connecting to payload remote socket");
|
||||
}
|
||||
@@ -257,63 +289,6 @@ public class LanLink extends BaseLink {
|
||||
packageReceived(np);
|
||||
}
|
||||
|
||||
static ServerSocket openTcpSocketOnFreePort(Context context, String deviceId, boolean useSsl) throws IOException {
|
||||
if (useSsl) {
|
||||
return openSecureServerSocket(context, deviceId);
|
||||
} else {
|
||||
return openUnsecureSocketOnFreePort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ServerSocket openUnsecureSocketOnFreePort() throws IOException {
|
||||
boolean success = false;
|
||||
int tcpPort = 1739;
|
||||
ServerSocket candidateServer = null;
|
||||
while(!success) {
|
||||
try {
|
||||
candidateServer = new ServerSocket();
|
||||
candidateServer.bind(new InetSocketAddress(tcpPort));
|
||||
success = true;
|
||||
Log.i("KDE/LanLink", "Using port "+tcpPort);
|
||||
} catch(IOException e) {
|
||||
//Log.e("LanLink", "Exception openning serversocket: "+e);
|
||||
tcpPort++;
|
||||
if (tcpPort >= 1764) {
|
||||
Log.e("KDE/LanLink", "No more ports available");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidateServer;
|
||||
}
|
||||
|
||||
static ServerSocket openSecureServerSocket(Context context, String deviceId) throws IOException{
|
||||
boolean success = false;
|
||||
int tcpPort = 1739;
|
||||
|
||||
SSLContext tlsContext = SslHelper.getSslContext(context, deviceId, true);
|
||||
SSLServerSocketFactory sslServerSocketFactory = tlsContext.getServerSocketFactory();
|
||||
|
||||
ServerSocket candidateServer = null;
|
||||
while(!success) {
|
||||
try {
|
||||
candidateServer = sslServerSocketFactory.createServerSocket();
|
||||
candidateServer.bind(new InetSocketAddress(tcpPort));
|
||||
success = true;
|
||||
Log.i("LanLink", "Using port "+tcpPort);
|
||||
} catch(IOException e) {
|
||||
//Log.e("LanLink", "Exception opening serversocket: "+e);
|
||||
tcpPort++;
|
||||
if (tcpPort >= 1764) {
|
||||
Log.e("LanLink", "No more ports available");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidateServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkShouldBeKeptAlive() {
|
||||
|
||||
@@ -321,14 +296,11 @@ public class LanLink extends BaseLink {
|
||||
//pairing to us, or connections that are already paired. TODO: Keep connections in the process of pairing
|
||||
|
||||
if (connectionSource == ConnectionStarted.Remotely) {
|
||||
//Log.e("LinkShouldBeKeptAlive", "because the other end started the connection");
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
if (preferences.contains(getDeviceId())) {
|
||||
return true; //Already paired
|
||||
}
|
||||
|
||||
//Log.e("LinkShouldBeKeptAlive", "false");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
@@ -21,9 +21,9 @@
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -32,435 +32,321 @@ import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Helpers.StringsHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.HandshakeCompletedEvent;
|
||||
import javax.net.ssl.HandshakeCompletedListener;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDisconnectedCallback {
|
||||
|
||||
public class LanLinkProvider extends BaseLinkProvider {
|
||||
public static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
|
||||
public static final int MIN_VERSION_WITH_NEW_PORT_SUPPORT = 7;
|
||||
|
||||
public static final String KEY_CUSTOM_DEVLIST_PREFERENCE = "device_list_preference";
|
||||
private final static int port = 1714;
|
||||
private static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
|
||||
final static int MIN_PORT_LEGACY = 1714;
|
||||
final static int MIN_PORT = 1716;
|
||||
final static int MAX_PORT = 1764;
|
||||
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final HashMap<String, LanLink> visibleComputers = new HashMap<>(); //Links by device id
|
||||
private final LongSparseArray<LanLink> nioLinks = new LongSparseArray<>(); //Links by channel id
|
||||
|
||||
private EventLoopGroup bossGroup, workerGroup, udpGroup, clientGroup;
|
||||
private TcpHandler tcpHandler = new TcpHandler();
|
||||
private UdpHandler udpHandler = new UdpHandler();
|
||||
private ServerSocket tcpServer;
|
||||
private DatagramSocket udpServer;
|
||||
private DatagramSocket udpServerOldPort;
|
||||
|
||||
// To prevent infinte loop if both device can only broadcast identity package but cannot connect via TCO
|
||||
private ArrayList<String> reverseConnectionBlackList = new ArrayList<>();
|
||||
private Timer reverseConnectionTimer;
|
||||
private boolean listening = false;
|
||||
|
||||
// To prevent infinte loop between Android < IceCream because both device can only broadcast identity package but cannot connect via TCP
|
||||
private ArrayList<InetAddress> reverseConnectionBlackList = new ArrayList<>();
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private class TcpHandler extends SimpleChannelInboundHandler<String>{
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
// Close channel for any sudden exception
|
||||
ctx.channel().close();
|
||||
@Override // SocketClosedCallback
|
||||
public void linkDisconnected(LanLink brokenLink) {
|
||||
String deviceId = brokenLink.getDeviceId();
|
||||
visibleComputers.remove(deviceId);
|
||||
connectionLost(brokenLink);
|
||||
}
|
||||
|
||||
//They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity.
|
||||
public void tcpPackageReceived(Socket socket) throws Exception {
|
||||
|
||||
NetworkPackage networkPackage;
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
String message = reader.readLine();
|
||||
networkPackage = NetworkPackage.unserialize(message);
|
||||
//Log.e("TcpListener","Received TCP package: "+networkPackage.serialize());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// Called after a long time if remote device closes session unexpectedly, like wifi off
|
||||
try {
|
||||
Channel channel = ctx.channel();
|
||||
final LanLink brokenLink = nioLinks.get(channel.hashCode());
|
||||
if (brokenLink != null) {
|
||||
nioLinks.remove(channel.hashCode());
|
||||
//Log.i("KDE/LanLinkProvider", "nioLinks.size(): " + nioLinks.size() + " (-)");
|
||||
try {
|
||||
brokenLink.closeSocket();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Exception. Already disconnected?");
|
||||
}
|
||||
//Log.i("KDE/LanLinkProvider", "Disconnected!");
|
||||
String deviceId = brokenLink.getDeviceId();
|
||||
if (visibleComputers.get(deviceId) == brokenLink) {
|
||||
visibleComputers.remove(deviceId);
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//Wait a bit before emitting connectionLost, in case the same device re-appears
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
connectionLost(brokenLink);
|
||||
if (!networkPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
Log.e("KDE/LanLinkProvider", "Expecting an identity package instead of " + networkPackage.getType());
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}).start();
|
||||
Log.i("KDE/LanLinkProvider", "Identity package received from a TCP connection from " + networkPackage.getString("deviceName"));
|
||||
identityPackageReceived(networkPackage, socket, LanLink.ConnectionStarted.Locally);
|
||||
}
|
||||
|
||||
//I've received their broadcast and should connect to their TCP socket and send my identity.
|
||||
protected void udpPacketReceived(DatagramPacket packet) throws Exception {
|
||||
|
||||
final InetAddress address = packet.getAddress();
|
||||
|
||||
try {
|
||||
|
||||
String message = new String(packet.getData(), StringsHelper.UTF8);
|
||||
final NetworkPackage identityPackage = NetworkPackage.unserialize(message);
|
||||
final String deviceId = identityPackage.getString("deviceId");
|
||||
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity package");
|
||||
return;
|
||||
} else {
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (deviceId.equals(myId)) {
|
||||
//Ignore my own broadcast
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "channelInactive exception");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, String message) throws Exception {
|
||||
//Log.e("KDE/LanLinkProvider", "Received a TCP packet from " + ctx.channel().remoteAddress() + ":" + message);
|
||||
|
||||
if (message.isEmpty()) {
|
||||
Log.e("KDE/LanLinkProvider", "Empty package received");
|
||||
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_NEW_PORT_SUPPORT && identityPackage.getInt("tcpPort") < MIN_PORT) {
|
||||
Log.w("KDE/LanLinkProvider", "Ignoring a udp broadcast from legacy port because it comes from a device which knows about the new port.");
|
||||
return;
|
||||
}
|
||||
|
||||
final Channel channel = ctx.channel();
|
||||
final NetworkPackage np = NetworkPackage.unserialize(message);
|
||||
Log.i("KDE/LanLinkProvider", "Broadcast identity package received from " + identityPackage.getString("deviceName"));
|
||||
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
int tcpPort = identityPackage.getInt("tcpPort", MIN_PORT);
|
||||
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (np.getString("deviceId").equals(myId)) {
|
||||
Log.e("KDE/LanLinkProvider", "Somehow I'm connected to myself, ignoring. This should not happen.");
|
||||
return;
|
||||
}
|
||||
SocketFactory socketFactory = SocketFactory.getDefault();
|
||||
Socket socket = socketFactory.createSocket(address, tcpPort);
|
||||
configureSocket(socket);
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Identity package received from a stablished TCP connection from " + np.getString("deviceName"));
|
||||
OutputStream out = socket.getOutputStream();
|
||||
NetworkPackage myIdentity = NetworkPackage.createIdentityPackage(context);
|
||||
out.write(myIdentity.serialize().getBytes());
|
||||
out.flush();
|
||||
|
||||
final LanLink.ConnectionStarted connectionStarted = LanLink.ConnectionStarted.Locally;
|
||||
identityPackageReceived(identityPackage, socket, LanLink.ConnectionStarted.Remotely);
|
||||
|
||||
// Add ssl handler if device uses new protocol
|
||||
try {
|
||||
if (np.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
|
||||
final SSLEngine sslEngine = SslHelper.getSslEngine(context, np.getString("deviceId"), SslHelper.SslMode.Client);
|
||||
|
||||
SslHandler sslHandler = new SslHandler(sslEngine);
|
||||
channel.pipeline().addFirst(sslHandler);
|
||||
sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<? super Channel>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Channel> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
Log.i("KDE/LanLinkProvider","Handshake successful with " + np.getString("deviceName") + " secured with " + sslEngine.getSession().getCipherSuite());
|
||||
//Log.e("KDE/LanLinkProvider", "Channel" + channel.hashCode());
|
||||
Certificate certificate = sslEngine.getSession().getPeerCertificates()[0];
|
||||
np.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
addLink(np, channel, connectionStarted, true);
|
||||
} else {
|
||||
// Unpair if handshake failed
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as server failed with " + np.getString("deviceName"));
|
||||
future.cause().printStackTrace();
|
||||
if (future.cause() instanceof SSLHandshakeException) {
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(np.getString("deviceId"));
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addLink(np, channel, connectionStarted, false);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address);
|
||||
e.printStackTrace();
|
||||
if (!reverseConnectionBlackList.contains(address)) {
|
||||
Log.w("KDE/LanLinkProvider","Blacklisting "+address);
|
||||
reverseConnectionBlackList.add(address);
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
reverseConnectionBlackList.remove(address);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 5*1000);
|
||||
|
||||
} else {
|
||||
LanLink link = nioLinks.get(channel.hashCode());
|
||||
if (link== null) {
|
||||
Log.e("KDE/LanLinkProvider","Expecting an identity package instead of " + np.getType());
|
||||
//Log.e("KDE/LanLinkProvider", "Channel" + channel.hashCode());
|
||||
} else {
|
||||
link.injectNetworkPackage(np);
|
||||
}
|
||||
// Try to cause a reverse connection
|
||||
onNetworkChange();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
private void configureSocket(Socket socket) {
|
||||
try {
|
||||
socket.setKeepAlive(true);
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(final ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
|
||||
try {
|
||||
String theMessage = packet.content().toString(CharsetUtil.UTF_8);
|
||||
private void identityPackageReceived(final NetworkPackage identityPackage, final Socket socket, final LanLink.ConnectionStarted connectionStarted) {
|
||||
|
||||
final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage);
|
||||
final String deviceId = identityPackage.getString("deviceId");
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
final String deviceId = identityPackage.getString("deviceId");
|
||||
if (deviceId.equals(myId)) {
|
||||
Log.e("KDE/LanLinkProvider", "Somehow I'm connected to myself, ignoring. This should not happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity package");
|
||||
return;
|
||||
} else {
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (deviceId.equals(myId)) {
|
||||
Log.i("KDE/LanLinkProvider", "Ignoring my own broadcast");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If I'm the TCP server I will be the SSL client and viceversa.
|
||||
final boolean clientMode = (connectionStarted == LanLink.ConnectionStarted.Locally);
|
||||
|
||||
//Log.i("KDE/LanLinkProvider", "Identity package received, creating link");
|
||||
// Add ssl handler if device uses new protocol
|
||||
try {
|
||||
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
|
||||
|
||||
try{
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(clientGroup);
|
||||
b.channel(NioSocketChannel.class);
|
||||
b.handler(new TcpInitializer());
|
||||
int tcpPort = identityPackage.getInt("tcpPort", port);
|
||||
final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort);
|
||||
channelFuture.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
|
||||
|
||||
final Channel channel = channelFuture.channel();
|
||||
Log.i("KDE/LanLinkProvider","Starting SSL handshake with " + identityPackage.getString("deviceName") + " trusted:"+isDeviceTrusted);
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + deviceId);
|
||||
if (!reverseConnectionBlackList.contains(deviceId)) {
|
||||
Log.w("KDE/LanLinkProvider","Blacklisting "+deviceId);
|
||||
reverseConnectionBlackList.add(deviceId);
|
||||
reverseConnectionTimer = new Timer();
|
||||
reverseConnectionTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
reverseConnectionBlackList.remove(deviceId);
|
||||
}
|
||||
}, 5*1000);
|
||||
|
||||
// Try to cause a reverse connection
|
||||
onNetworkChange();
|
||||
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||
sslsocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
|
||||
@Override
|
||||
public void handshakeCompleted(HandshakeCompletedEvent event) {
|
||||
String mode = clientMode? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider","Handshake as " + mode + " successful with " + identityPackage.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPackage, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Handshake as " + mode + " failed with " + identityPackage.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Log.e("KDE/LanLinkProvider", "Connection successful: " + channel.isActive());
|
||||
|
||||
// Add ssl handler if device supports new protocol
|
||||
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
|
||||
// add ssl handler with start tls true
|
||||
SSLEngine sslEngine = SslHelper.getSslEngine(context, deviceId, SslHelper.SslMode.Server);
|
||||
SslHandler sslHandler = new SslHandler(sslEngine, true);
|
||||
channel.pipeline().addFirst(sslHandler);
|
||||
}
|
||||
|
||||
final LanLink.ConnectionStarted connectionStarted = LanLink.ConnectionStarted.Remotely;
|
||||
|
||||
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
|
||||
ChannelFuture future2 = channel.writeAndFlush(np2.serialize()).sync();
|
||||
if (!future2.isSuccess()) {
|
||||
Log.e("KDE/LanLinkProvider", "Connection failed: could not send identity package back");
|
||||
return;
|
||||
}
|
||||
|
||||
// If ssl handler is in channel, add link after handshake is completed
|
||||
final SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
|
||||
if (sslHandler != null) {
|
||||
//Log.e("KDE/LanLinkProvider", "Initiating SSL handshake");
|
||||
sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<? super Channel>>() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(Future<? super Channel> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
try {
|
||||
Log.i("KDE/LanLinkProvider", "Handshake successfully completed with " + identityPackage.getString("deviceName") + ", session secured with " + sslHandler.engine().getSession().getCipherSuite());
|
||||
Certificate certificate = sslHandler.engine().getSession().getPeerCertificates()[0];
|
||||
identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
addLink(identityPackage, channel, connectionStarted, true);
|
||||
} catch (Exception e){
|
||||
Log.e("KDE/LanLinkProvider", "Exception in addLink");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
// Unpair if handshake failed
|
||||
// Any exception or handshake exception ?
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as client failed with " + identityPackage.getString("deviceName"));
|
||||
future.cause().printStackTrace();
|
||||
if (future.cause() instanceof SSLHandshakeException) {
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.w("KDE/LanLinkProvider", "Not using SSL");
|
||||
addLink(identityPackage, channel, connectionStarted, false);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Exception receiving udp package!!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sslsocket.startHandshake();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
addLink(identityPackage, socket, connectionStarted);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TcpInitializer extends ChannelInitializer<SocketChannel> {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
ch.config().setAllowHalfClosure(false); // Not sure how it will work, but we certainly don't want half closure
|
||||
ch.config().setKeepAlive(true);
|
||||
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
|
||||
pipeline.addLast(new StringDecoder());
|
||||
pipeline.addLast(new StringEncoder());
|
||||
pipeline.addLast(tcpHandler);
|
||||
}
|
||||
}
|
||||
private void addLink(final NetworkPackage identityPackage, Socket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
|
||||
|
||||
private void addLink(NetworkPackage identityPackage, Channel channel, LanLink.ConnectionStarted connectionOrigin, boolean useSsl) {
|
||||
String deviceId = identityPackage.getString("deviceId");
|
||||
Log.i("KDE/LanLinkProvider","addLink to "+deviceId);
|
||||
LanLink currentLink = visibleComputers.get(deviceId);
|
||||
if (currentLink != null) {
|
||||
//Update old link
|
||||
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
|
||||
final Channel oldChannel = currentLink.reset(channel, connectionOrigin, useSsl);
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
nioLinks.remove(oldChannel.hashCode());
|
||||
//Log.e("KDE/LanLinkProvider", "Forgetting about channel " + channel.hashCode());
|
||||
}
|
||||
}, 500); //Stop accepting messages from the old channel after 500ms
|
||||
nioLinks.put(channel.hashCode(), currentLink);
|
||||
//Log.e("KDE/LanLinkProvider", "Replacing channel. old: "+ oldChannel.hashCode() + " - new: "+ channel.hashCode());
|
||||
final Socket oldSocket = currentLink.reset(socket, connectionOrigin);
|
||||
//Log.e("KDE/LanLinkProvider", "Replacing socket. old: "+ oldSocket.hashCode() + " - new: "+ socket.hashCode());
|
||||
} else {
|
||||
Log.i("KDE/LanLinkProvider", "Creating a new link for device " + deviceId);
|
||||
//Let's create the link
|
||||
LanLink link = new LanLink(context, deviceId, this, channel, connectionOrigin, useSsl);
|
||||
nioLinks.put(channel.hashCode(), link);
|
||||
LanLink link = new LanLink(context, deviceId, this, socket, connectionOrigin);
|
||||
visibleComputers.put(deviceId, link);
|
||||
connectionAccepted(identityPackage, link);
|
||||
}
|
||||
}
|
||||
|
||||
public LanLinkProvider(Context context) {
|
||||
|
||||
this.context = context;
|
||||
|
||||
udpGroup = new NioEventLoopGroup();
|
||||
try {
|
||||
Bootstrap udpBootstrap = new Bootstrap();
|
||||
udpBootstrap.group(udpGroup);
|
||||
udpBootstrap.channel(NioDatagramChannel.class);
|
||||
udpBootstrap.option(ChannelOption.SO_BROADCAST, true);
|
||||
udpBootstrap.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
|
||||
pipeline.addLast(new StringDecoder());
|
||||
pipeline.addLast(new StringEncoder());
|
||||
pipeline.addLast(udpHandler);
|
||||
}
|
||||
});
|
||||
udpBootstrap.bind(new InetSocketAddress(port)).sync();
|
||||
}catch (Exception e){
|
||||
Log.e("KDE/LanLinkProvider","Exception setting up UDP server");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
clientGroup = new NioEventLoopGroup();
|
||||
|
||||
// Due to certificate request from SSL server to client, the certificate request message from device with latest android version to device with
|
||||
// old android version causes a FATAL ALERT message stating that incorrect certificate request
|
||||
// Server is disabled on these devices and using a reverse connection strategy. This works well for connection of these devices with kde
|
||||
// and newer android versions. Although devices with android version less than ICS cannot connect to other devices who also have android version less
|
||||
// than ICS because server is disabled on both
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
Log.w("KDE/LanLinkProvider","Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
return;
|
||||
}
|
||||
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
try{
|
||||
ServerBootstrap tcpBootstrap = new ServerBootstrap();
|
||||
tcpBootstrap.group(bossGroup, workerGroup);
|
||||
tcpBootstrap.channel(NioServerSocketChannel.class);
|
||||
tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100);
|
||||
tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO));
|
||||
tcpBootstrap.option(ChannelOption.SO_REUSEADDR, true);
|
||||
tcpBootstrap.childHandler(new TcpInitializer());
|
||||
tcpBootstrap.bind(new InetSocketAddress(port)).sync();
|
||||
}catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Exception setting up TCP server");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
private DatagramSocket setupUdpListener(int udpPort) {
|
||||
final DatagramSocket server;
|
||||
try {
|
||||
server = new DatagramSocket(udpPort);
|
||||
server.setReuseAddress(true);
|
||||
server.setBroadcast(true);
|
||||
} catch (SocketException e) {
|
||||
Log.e("LanLinkProvider", "Error creating udp server");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (listening) {
|
||||
final int bufferSize = 1024 * 512;
|
||||
byte[] data = new byte[bufferSize];
|
||||
DatagramPacket packet = new DatagramPacket(data, bufferSize);
|
||||
try {
|
||||
server.receive(packet);
|
||||
udpPacketReceived(packet);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "UdpReceive exception");
|
||||
}
|
||||
}
|
||||
Log.w("UdpListener","Stopping UDP listener");
|
||||
}
|
||||
}).start();
|
||||
return server;
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "onStart");
|
||||
private void setupTcpListener() {
|
||||
|
||||
try {
|
||||
tcpServer = openServerSocketOnFreePort(MIN_PORT);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (listening) {
|
||||
try {
|
||||
Socket socket = tcpServer.accept();
|
||||
configureSocket(socket);
|
||||
tcpPackageReceived(socket);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "TcpReceive exception");
|
||||
}
|
||||
}
|
||||
Log.w("TcpListener", "Stopping TCP listener");
|
||||
}
|
||||
}).start();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
|
||||
int tcpPort = minPort;
|
||||
while(tcpPort < MAX_PORT) {
|
||||
try {
|
||||
ServerSocket candidateServer = new ServerSocket();
|
||||
candidateServer.bind(new InetSocketAddress(tcpPort));
|
||||
Log.i("KDE/LanLink", "Using port "+tcpPort);
|
||||
return candidateServer;
|
||||
} catch(IOException e) {
|
||||
tcpPort++;
|
||||
}
|
||||
}
|
||||
Log.e("KDE/LanLink", "No ports available");
|
||||
throw new IOException("No ports available");
|
||||
}
|
||||
|
||||
void broadcastUdpPackage() {
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(
|
||||
KEY_CUSTOM_DEVLIST_PREFERENCE, "");
|
||||
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(CustomDevicesActivity.KEY_CUSTOM_DEVLIST_PREFERENCE, "");
|
||||
ArrayList<String> iplist = new ArrayList<>();
|
||||
if (!deviceListPrefs.isEmpty()) {
|
||||
iplist = CustomDevicesActivity.deserializeIpList(deviceListPrefs);
|
||||
@@ -468,14 +354,14 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
iplist.add("255.255.255.255"); //Default: broadcast.
|
||||
|
||||
NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
|
||||
identity.set("tcpPort", port);
|
||||
identity.set("tcpPort", MIN_PORT);
|
||||
DatagramSocket socket = null;
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
socket.setReuseAddress(true);
|
||||
socket.setBroadcast(true);
|
||||
bytes = identity.serialize().getBytes("UTF-8");
|
||||
bytes = identity.serialize().getBytes(StringsHelper.UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider","Failed to create DatagramSocket");
|
||||
@@ -486,9 +372,9 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
for (String ipstr : iplist) {
|
||||
try {
|
||||
InetAddress client = InetAddress.getByName(ipstr);
|
||||
java.net.DatagramPacket packet = new java.net.DatagramPacket(bytes, bytes.length, client, port);
|
||||
socket.send(packet);
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+packet.getAddress());
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT_LEGACY));
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+client);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Sending udp identity package failed. Invalid address? (" + ipstr + ")");
|
||||
@@ -503,23 +389,53 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@Override
|
||||
public void onStart() {
|
||||
//Log.i("KDE/LanLinkProvider", "onStart");
|
||||
if (!listening) {
|
||||
|
||||
listening = true;
|
||||
|
||||
udpServer = setupUdpListener(MIN_PORT);
|
||||
udpServerOldPort = setupUdpListener(MIN_PORT_LEGACY);
|
||||
|
||||
// Due to certificate request from SSL server to client, the certificate request message from device with latest android version to device with
|
||||
// old android version causes a FATAL ALERT message stating that incorrect certificate request
|
||||
// Server is disabled on these devices and using a reverse connection strategy. This works well for connection of these devices with kde
|
||||
// and newer android versions. Although devices with android version less than ICS cannot connect to other devices who also have android version less
|
||||
// than ICS because server is disabled on both
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
Log.w("KDE/LanLinkProvider","Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
} else {
|
||||
setupTcpListener();
|
||||
}
|
||||
|
||||
broadcastUdpPackage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkChange() {
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
onStart();
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
broadcastUdpPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
//Log.i("KDE/LanLinkProvider", "onStop");
|
||||
listening = false;
|
||||
try {
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
udpGroup.shutdownGracefully();
|
||||
clientGroup.shutdownGracefully();
|
||||
}catch (Exception e){
|
||||
tcpServer.close();
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServer.close();
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServerOldPort.close();
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -529,6 +445,4 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
return "LanLinkProvider";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -202,11 +202,13 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
//Log.e("KDE/PairingDone", "Pairing Done");
|
||||
SharedPreferences.Editor editor = mDevice.getContext().getSharedPreferences(mDevice.getDeviceId(), Context.MODE_PRIVATE).edit();
|
||||
|
||||
try {
|
||||
String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/PairingDone", "Error encoding public key");
|
||||
if (mDevice.publicKey != null) {
|
||||
try {
|
||||
String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/PairingDone", "Error encoding public key");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
@@ -26,11 +26,9 @@ import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LoopbackLink extends BaseLink {
|
||||
|
||||
|
@@ -20,7 +20,6 @@
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -64,11 +63,13 @@ public class BackgroundService extends Service {
|
||||
if (wasEmpty) {
|
||||
onNetworkChange();
|
||||
}
|
||||
//Log.e("acquireDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]");
|
||||
return wasEmpty;
|
||||
}
|
||||
|
||||
public void releaseDiscoveryMode(Object key) {
|
||||
boolean removed = discoveryModeAcquisitions.remove(key);
|
||||
//Log.e("releaseDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]");
|
||||
if (removed && discoveryModeAcquisitions.isEmpty()) {
|
||||
cleanDevices();
|
||||
}
|
||||
@@ -162,11 +163,16 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
|
||||
private void cleanDevices() {
|
||||
for(Device d : devices.values()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
|
||||
d.disconnect();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for(Device d : devices.values()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
|
||||
d.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
|
||||
|
@@ -28,17 +28,15 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.Helpers.ObjectsHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
@@ -58,7 +56,6 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -74,11 +71,13 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
private int notificationId;
|
||||
private int protocolVersion;
|
||||
|
||||
private static final int MIN_VERSION_WITH_CAPPABILITIES_SUPPORT = 6;
|
||||
|
||||
private DeviceType deviceType;
|
||||
private PairStatus pairStatus;
|
||||
|
||||
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
|
||||
private Map<String, BasePairingHandler> pairingHandlers = new HashMap<String, BasePairingHandler>();
|
||||
private Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
|
||||
|
||||
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
||||
|
||||
@@ -572,10 +571,11 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPackageReceived()");
|
||||
//try { Log.e("KDE/Device", "NetworkPackage:" + np.serialize()); } catch (Exception _) { }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
|
||||
Log.w("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -643,9 +643,9 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
hackToMakeRetrocompatiblePacketTypes(np);
|
||||
|
||||
if (protocolVersion >= 6 && !supportedOutgoingInterfaces.contains(np.getType())) {
|
||||
if (protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT && !supportedOutgoingInterfaces.contains(np.getType()) && !NetworkPackage.protocolPackageTypes.contains(np.getType())) {
|
||||
Log.e("Device/sendPackage", "Plugin tried to send an unsupported package: " + np.getType());
|
||||
Log.e("Device/sendPackage", "Supported package types: " + Arrays.toString(supportedOutgoingInterfaces.toArray()));
|
||||
Log.w("Device/sendPackage", "Supported package types: " + Arrays.toString(supportedOutgoingInterfaces.toArray()));
|
||||
}
|
||||
|
||||
//Log.e("sendPackage", "Sending package...");
|
||||
@@ -656,7 +656,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
boolean useEncryption = (protocolVersion < 6 && (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()));
|
||||
boolean useEncryption = (protocolVersion < LanLinkProvider.MIN_VERSION_WITH_SSL_SUPPORT && (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()));
|
||||
|
||||
//Make a copy to avoid concurrent modification exception if the original list changes
|
||||
for (final BaseLink link : links) {
|
||||
@@ -790,7 +790,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
HashMap<String, ArrayList<String>> newPluginsByIncomingInterface = new HashMap<>();
|
||||
HashMap<String, ArrayList<String>> newPluginsByOutgoingInterface = new HashMap<>();
|
||||
|
||||
final boolean supportsCapabilities = (protocolVersion >= 6);
|
||||
final boolean supportsCapabilities = (protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT);
|
||||
|
||||
for (String pluginKey : availablePlugins) {
|
||||
|
||||
@@ -865,12 +865,14 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
supportedOutgoingInterfaces = newSupportedOutgoingInterfaces;
|
||||
unsupportedPlugins = newUnsupportedPlugins;
|
||||
|
||||
Log.i("ReloadPlugins", "not loading " + Arrays.toString(unsupportedPlugins.toArray()) + " because of unmatched capabilities");
|
||||
if (!unsupportedPlugins.isEmpty()) {
|
||||
Log.i("ReloadPlugins", "not loading " + Arrays.toString(unsupportedPlugins.toArray()) + " because of unmatched capabilities");
|
||||
}
|
||||
|
||||
onPluginsChanged();
|
||||
|
||||
//Only send capabilities to devices using protocol version 6 or later
|
||||
if (capabilitiesChanged && isReachable() && isPaired() && protocolVersion >= 6) {
|
||||
if (capabilitiesChanged && isReachable() && isPaired() && protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CAPABILITIES);
|
||||
np.set("IncomingCapabilities", new ArrayList<>(newSupportedIncomingInterfaces));
|
||||
np.set("OutgoingCapabilities", new ArrayList<>(newSupportedOutgoingInterfaces));
|
||||
@@ -908,6 +910,13 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
}
|
||||
|
||||
public boolean deviceShouldBeKeptAlive() {
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
if (preferences.contains(getDeviceId())) {
|
||||
//Log.e("DeviceShouldBeKeptAlive", "because it's a paired device");
|
||||
return true; //Already paired
|
||||
}
|
||||
|
||||
for(BaseLink l : links) {
|
||||
if (l.linkShouldBeKeptAlive()) {
|
||||
return true;
|
||||
@@ -917,12 +926,12 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
}
|
||||
|
||||
public void hackToMakeRetrocompatiblePacketTypes(NetworkPackage np) {
|
||||
if (protocolVersion >= 6) return;
|
||||
if (protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT) return;
|
||||
np.mType = np.getType().replace(".request","");
|
||||
}
|
||||
|
||||
public String hackToMakeRetrocompatiblePacketTypes(String type) {
|
||||
if (protocolVersion >= 6) return type;
|
||||
if (protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT) return type;
|
||||
return type.replace(".request","");
|
||||
}
|
||||
|
||||
|
@@ -20,10 +20,11 @@
|
||||
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Base64;
|
||||
@@ -37,11 +38,13 @@ import java.util.Map;
|
||||
|
||||
public class ContactsHelper {
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static Map<String, String> phoneNumberLookup(Context context, String number) {
|
||||
|
||||
//Log.e("PhoneNumberLookup", number);
|
||||
|
||||
Map<String, String> contactInfo = new HashMap<String, String>();
|
||||
Map<String, String> contactInfo = new HashMap<>();
|
||||
|
||||
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
||||
Cursor cursor = null;
|
||||
@@ -82,25 +85,29 @@ public class ContactsHelper {
|
||||
}
|
||||
|
||||
public static String photoId64Encoded(Context context, String photoId) {
|
||||
if (photoId == null) {
|
||||
return "";
|
||||
}
|
||||
Uri photoUri = Uri.parse(photoId);
|
||||
Uri displayPhotoUri = Uri.withAppendedPath(photoUri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
|
||||
|
||||
byte[] buffer = null;
|
||||
Base64OutputStream out = null;
|
||||
ByteArrayOutputStream encodedPhoto = null;
|
||||
InputStream input = null;
|
||||
Base64OutputStream output= null;
|
||||
try {
|
||||
encodedPhoto = new ByteArrayOutputStream();
|
||||
out = new Base64OutputStream(encodedPhoto, Base64.DEFAULT);
|
||||
InputStream fd2 = context.getContentResolver().openInputStream(photoUri);
|
||||
buffer = new byte[1024];
|
||||
ByteArrayOutputStream encodedPhoto = new ByteArrayOutputStream();
|
||||
output = new Base64OutputStream(encodedPhoto, Base64.DEFAULT);
|
||||
input = context.getContentResolver().openInputStream(photoUri);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = fd2.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
while ((len = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, len);
|
||||
}
|
||||
return encodedPhoto.toString();
|
||||
} catch (Exception ex) {
|
||||
Log.e("ContactsHelper", ex.toString());
|
||||
return new String();
|
||||
return "";
|
||||
} finally {
|
||||
try { input.close(); } catch(Exception ignored) { };
|
||||
try { output.close(); } catch(Exception ignored) { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class NotificationsHelper {
|
||||
|
||||
private final static AtomicInteger c = new AtomicInteger((int)System.currentTimeMillis());
|
||||
public static int getUniqueId() {
|
||||
return c.incrementAndGet();
|
||||
}
|
||||
|
||||
}
|
@@ -1,8 +1,6 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class RandomHelper {
|
||||
|
@@ -23,7 +23,6 @@ package org.kde.kdeconnect.Helpers.SecurityHelpers;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
|
@@ -24,11 +24,11 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.spongycastle.asn1.x500.X500NameBuilder;
|
||||
import org.spongycastle.asn1.x500.style.BCStyle;
|
||||
import org.spongycastle.cert.X509CertificateHolder;
|
||||
@@ -38,10 +38,10 @@ import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.operator.ContentSigner;
|
||||
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
@@ -50,14 +50,14 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Formatter;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
@@ -132,6 +132,7 @@ public class SslHelper {
|
||||
}
|
||||
|
||||
public static SSLContext getSslContext(Context context, String deviceId, boolean isDeviceTrusted) {
|
||||
//TODO: Cache
|
||||
try {
|
||||
// Get device private key
|
||||
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
||||
@@ -141,7 +142,6 @@ public class SslHelper {
|
||||
if (isDeviceTrusted){
|
||||
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
byte[] certificateBytes = Base64.decode(devicePreferences.getString("certificate", ""), 0);
|
||||
Log.e("DeviceCertificate", "bytes:"+ Arrays.toString(certificateBytes));
|
||||
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
|
||||
remoteDeviceCertificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
|
||||
}
|
||||
@@ -196,48 +196,41 @@ public class SslHelper {
|
||||
|
||||
}
|
||||
|
||||
public static SSLEngine getSslEngine(final Context context, final String deviceId, SslMode sslMode) {
|
||||
public static void configureSslSocket(SSLSocket socket, boolean isDeviceTrusted, boolean isClient) {
|
||||
|
||||
try{
|
||||
socket.setEnabledProtocols(new String[]{ "TLSv1" }); //Newer TLS versions are only supported on API 16+
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
final boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
|
||||
|
||||
SSLContext tlsContext = getSslContext(context, deviceId, isDeviceTrusted);
|
||||
SSLEngine sslEngine = tlsContext.createSSLEngine();
|
||||
|
||||
sslEngine.setEnabledProtocols(new String[]{ "TLSv1" }); //Newer TLS versions are only supported on API 16+
|
||||
|
||||
// These cipher suites are most common of them that are accepted by kde and android during handshake
|
||||
ArrayList<String> supportedCiphers = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
|
||||
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
|
||||
supportedCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
|
||||
}
|
||||
// These cipher suites are most common of them that are accepted by kde and android during handshake
|
||||
ArrayList<String> supportedCiphers = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
|
||||
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
|
||||
supportedCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
|
||||
} else {
|
||||
// Following ciphers are for and due to old devices
|
||||
supportedCiphers.add("SSL_RSA_WITH_RC4_128_SHA");
|
||||
supportedCiphers.add("SSL_RSA_WITH_RC4_128_MD5");
|
||||
sslEngine.setEnabledCipherSuites(supportedCiphers.toArray(new String[supportedCiphers.size()]));
|
||||
|
||||
|
||||
if (sslMode == SslMode.Client){
|
||||
sslEngine.setUseClientMode(true);
|
||||
}else{
|
||||
sslEngine.setUseClientMode(false);
|
||||
if (isDeviceTrusted) {
|
||||
sslEngine.setNeedClientAuth(true);
|
||||
}else {
|
||||
sslEngine.setWantClientAuth(true);
|
||||
}
|
||||
}
|
||||
|
||||
return sslEngine;
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
Log.e("SslHelper", "Error creating ssl filter");
|
||||
}
|
||||
return null;
|
||||
socket.setEnabledCipherSuites(supportedCiphers.toArray(new String[supportedCiphers.size()]));
|
||||
|
||||
if (isClient){
|
||||
socket.setUseClientMode(true);
|
||||
}else{
|
||||
socket.setUseClientMode(false);
|
||||
if (isDeviceTrusted) {
|
||||
socket.setNeedClientAuth(true);
|
||||
} else {
|
||||
socket.setWantClientAuth(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static SSLSocket convertToSslSocket(Context context, Socket socket, String deviceId, boolean isDeviceTrusted, boolean clientMode) throws IOException {
|
||||
SSLSocketFactory sslsocketFactory = SslHelper.getSslContext(context, deviceId, isDeviceTrusted).getSocketFactory();
|
||||
SSLSocket sslsocket = (SSLSocket)sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
|
||||
SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
|
||||
return sslsocket;
|
||||
}
|
||||
|
||||
public static String getCertificateHash(Certificate certificate) {
|
||||
@@ -253,7 +246,6 @@ public class SslHelper {
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Certificate parseCertificate(byte[] certificateBytes) throws IOException, CertificateException {
|
||||
|
9
src/org/kde/kdeconnect/Helpers/StringsHelper.java
Normal file
9
src/org/kde/kdeconnect/Helpers/StringsHelper.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class StringsHelper {
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
}
|
@@ -21,8 +21,6 @@
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
@@ -33,15 +31,24 @@ import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class NetworkPackage {
|
||||
|
||||
public final static int ProtocolVersion = 6;
|
||||
public final static int ProtocolVersion = 7;
|
||||
|
||||
public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity";
|
||||
public final static String PACKAGE_TYPE_PAIR = "kdeconnect.pair";
|
||||
public final static String PACKAGE_TYPE_ENCRYPTED = "kdeconnect.encrypted";
|
||||
public static final String PACKAGE_TYPE_CAPABILITIES = "kdeconnect.capabilities";
|
||||
public final static String PACKAGE_TYPE_CAPABILITIES = "kdeconnect.capabilities";
|
||||
|
||||
public static Set<String> protocolPackageTypes = new HashSet<String>() {{
|
||||
add(PACKAGE_TYPE_IDENTITY);
|
||||
add(PACKAGE_TYPE_PAIR);
|
||||
add(PACKAGE_TYPE_ENCRYPTED);
|
||||
add(PACKAGE_TYPE_CAPABILITIES);
|
||||
}};
|
||||
|
||||
private long mId;
|
||||
String mType;
|
||||
@@ -121,8 +128,6 @@ public class NetworkPackage {
|
||||
}
|
||||
public boolean has(String key) { return mBody.has(key); }
|
||||
|
||||
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); }
|
||||
|
||||
public String serialize() throws JSONException {
|
||||
JSONObject jo = new JSONObject();
|
||||
jo.put("id", mId);
|
||||
|
@@ -22,8 +22,8 @@ package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
@@ -20,12 +20,6 @@
|
||||
|
||||
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
@@ -1,11 +1,6 @@
|
||||
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
|
@@ -1,10 +1,6 @@
|
||||
package org.kde.kdeconnect.Plugins.MousePadPlugin;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
public class KeyInputConnection extends BaseInputConnection {
|
||||
private KeyListenerView view;
|
||||
|
@@ -28,12 +28,12 @@ import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
@@ -42,7 +42,8 @@ import org.kde.kdeconnect_tp.R;
|
||||
public class MousePadActivity extends ActionBarActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener {
|
||||
String deviceId;
|
||||
|
||||
private final static float MinDistanceToSendScroll = 2.5f;
|
||||
private final static float MinDistanceToSendScroll = 2.5f; // touch gesture scroll
|
||||
private final static float MinDistanceToSendGenericScroll = 0.1f; // real mouse scroll wheel event
|
||||
|
||||
private float mPrevX;
|
||||
private float mPrevY;
|
||||
@@ -122,6 +123,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
mCurrentSensitivity = 2.0f;
|
||||
break;
|
||||
default:
|
||||
mCurrentSensitivity = 1.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -237,6 +239,25 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent e)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 12) { // MotionEvent.getAxisValue is >= 12
|
||||
if (e.getAction() == MotionEvent.ACTION_SCROLL) {
|
||||
final float distanceY = e.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||
|
||||
accumulatedDistanceY += distanceY;
|
||||
|
||||
if (accumulatedDistanceY > MinDistanceToSendGenericScroll || accumulatedDistanceY < -MinDistanceToSendGenericScroll) {
|
||||
sendScroll(accumulatedDistanceY);
|
||||
accumulatedDistanceY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onGenericMotionEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) {
|
||||
// If only one thumb is used then cancel the scroll gesture
|
||||
@@ -249,17 +270,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
accumulatedDistanceY += distanceY;
|
||||
if (accumulatedDistanceY > MinDistanceToSendScroll || accumulatedDistanceY < -MinDistanceToSendScroll)
|
||||
{
|
||||
final float scrollToSendY = accumulatedDistanceY;
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendScroll(0, scrollDirection * scrollToSendY);
|
||||
}
|
||||
});
|
||||
sendScroll(scrollDirection * accumulatedDistanceY);
|
||||
|
||||
accumulatedDistanceY = 0;
|
||||
}
|
||||
@@ -386,6 +397,18 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
});
|
||||
}
|
||||
|
||||
private void sendScroll(final float y) {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendScroll(0, y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInputFromWindow(keyListenerView.getWindowToken(), 0, 0);
|
||||
|
@@ -84,6 +84,10 @@ public class MousePadPlugin extends Plugin {
|
||||
public void sendMouseDelta(float dx, float dy, float sensitivity) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
|
||||
if (sensitivity <= 0.0001f) {
|
||||
sensitivity = 1.0f;
|
||||
}
|
||||
|
||||
np.set("dx", dx*sensitivity);
|
||||
np.set("dy", dy*sensitivity);
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package org.kde.kdeconnect.Plugins.MprisPlugin;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -33,14 +34,15 @@ import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
@@ -93,6 +95,13 @@ public class MprisActivity extends ActionBarActivity {
|
||||
TextView nowPlaying = (TextView) findViewById(R.id.now_playing_textview);
|
||||
if (!nowPlaying.getText().toString().equals(song)) {
|
||||
nowPlaying.setText(song);
|
||||
|
||||
Bitmap currentArt = mpris.getCurrentArt();
|
||||
ImageView artView = (ImageView) findViewById(R.id.artImageView);
|
||||
if (currentArt != null) {
|
||||
artView.setImageBitmap(currentArt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mpris.getLength() > -1 && mpris.getPosition() > -1 && !"spotify".equals(mpris.getPlayer().toLowerCase())) {
|
||||
|
@@ -22,10 +22,13 @@ package org.kde.kdeconnect.Plugins.MprisPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
@@ -43,6 +46,7 @@ public class MprisPlugin extends Plugin {
|
||||
private String player = "";
|
||||
private boolean playing = false;
|
||||
private String currentSong = "";
|
||||
private Bitmap currentArt;
|
||||
private int volume = 50;
|
||||
private long length = -1;
|
||||
private long lastPosition;
|
||||
@@ -120,7 +124,8 @@ public class MprisPlugin extends Plugin {
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
|
||||
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") || np.has("pos")) {
|
||||
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") ||
|
||||
np.has("pos") || np.has("artImage")) {
|
||||
if (np.getString("player").equals(player)) {
|
||||
currentSong = np.getString("nowPlaying", currentSong);
|
||||
volume = np.getInt("volume", volume);
|
||||
@@ -129,6 +134,11 @@ public class MprisPlugin extends Plugin {
|
||||
lastPosition = np.getLong("pos", lastPosition);
|
||||
lastPositionTime = System.currentTimeMillis();
|
||||
}
|
||||
if (np.has("artImage")) {
|
||||
String base64Image = np.getString("artImage");
|
||||
byte[] decodedBytes = Base64.decode(base64Image, 0);
|
||||
currentArt = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
|
||||
}
|
||||
playing = np.getBoolean("isPlaying", playing);
|
||||
for (String key : playerStatusUpdated.keySet()) {
|
||||
try {
|
||||
@@ -142,9 +152,8 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
if (np.has("playerList")) {
|
||||
|
||||
ArrayList<String> newPlayerList = np.getStringList("playerList");
|
||||
ArrayList<String> newPlayerList = np.getStringList("playerList");
|
||||
if (newPlayerList != null) {
|
||||
boolean equals = false;
|
||||
if (newPlayerList.size() == playerList.size()) {
|
||||
equals = true;
|
||||
@@ -208,6 +217,7 @@ public class MprisPlugin extends Plugin {
|
||||
if (player == null || player.equals(this.player)) return;
|
||||
this.player = player;
|
||||
currentSong = "";
|
||||
currentArt = null;
|
||||
volume = 50;
|
||||
playing = false;
|
||||
for (String key : playerStatusUpdated.keySet()) {
|
||||
@@ -230,6 +240,8 @@ public class MprisPlugin extends Plugin {
|
||||
return currentSong;
|
||||
}
|
||||
|
||||
public Bitmap getCurrentArt() { return currentArt; }
|
||||
|
||||
public String getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
@@ -24,30 +24,21 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
|
||||
|
||||
@@ -111,11 +102,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// request all existing notifications
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
|
||||
np.set("request", true);
|
||||
device.sendPackage(np);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,7 +121,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
|
||||
String id = getNotificationKeyCompat(statusBarNotification);
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION);
|
||||
np.set("id", id);
|
||||
np.set("isCancel", true);
|
||||
device.sendPackage(np);
|
||||
@@ -324,58 +310,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (!np.has("ticker") || !np.has("appName") || !np.has("id")) {
|
||||
Log.e("NotificationsPlugin", "Received notification package lacks properties");
|
||||
} else {
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MaterialActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
Bitmap largeIcon = null;
|
||||
if (np.hasPayload()) {
|
||||
int width = 64; // default icon dimensions
|
||||
int height = 64;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
|
||||
height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
|
||||
}
|
||||
final InputStream input = np.getPayload();
|
||||
largeIcon = BitmapFactory.decodeStream(np.getPayload());
|
||||
try { input.close(); } catch (Exception e) { }
|
||||
if (largeIcon != null) {
|
||||
//Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height);
|
||||
if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) {
|
||||
// older API levels don't scale notification icons automatically, therefore:
|
||||
largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(np.getString("appName"))
|
||||
.setContentText(np.getString("ticker"))
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(np.getString("ticker"))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setAutoCancel(true)
|
||||
.setLocalOnly(true) // to avoid bouncing the notification back to other kdeconnect nodes
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
try {
|
||||
// tag all incoming notifications
|
||||
notificationManager.notify("kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
|
||||
} catch (Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -419,12 +353,12 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION};
|
||||
}
|
||||
|
||||
//For compat with API<21, because lollipop changed the way to cancel notifications
|
||||
@@ -433,6 +367,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
service.cancelNotification(compatKey);
|
||||
} else {
|
||||
int first = compatKey.indexOf(':');
|
||||
if (first == -1) {
|
||||
Log.e("cancelNotificationCompa","Not formated like a notification key: "+ compatKey);
|
||||
return;
|
||||
}
|
||||
int last = compatKey.lastIndexOf(':');
|
||||
String packageName = compatKey.substring(0, first);
|
||||
String tag = compatKey.substring(first + 1, last);
|
||||
|
@@ -31,8 +31,8 @@ import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
|
||||
|
@@ -26,25 +26,23 @@ import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhonePlugin;
|
||||
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
|
||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin.ReceiveNotificationsPlugin;
|
||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
|
||||
import org.kde.kdeconnect.Plugins.TelepathyPlugin.TelepathyPlugin;
|
||||
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class PluginFactory {
|
||||
|
||||
@@ -120,6 +118,7 @@ public class PluginFactory {
|
||||
PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
PluginFactory.registerPlugin(SftpPlugin.class);
|
||||
PluginFactory.registerPlugin(NotificationsPlugin.class);
|
||||
PluginFactory.registerPlugin(ReceiveNotificationsPlugin.class);
|
||||
PluginFactory.registerPlugin(MousePadPlugin.class);
|
||||
PluginFactory.registerPlugin(SharePlugin.class);
|
||||
//PluginFactory.registerPlugin(TelepathyPlugin.class);
|
||||
|
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ReceiveNotificationsPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_receive_notifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getResources().getString(R.string.pref_plugin_receive_notifications_desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
// request all existing notifications
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
|
||||
np.set("request", true);
|
||||
device.sendPackage(np);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(final NetworkPackage np) {
|
||||
|
||||
if (!np.has("ticker") || !np.has("appName") || !np.has("id")) {
|
||||
Log.e("NotificationsPlugin", "Received notification package lacks properties");
|
||||
} else {
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MaterialActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
Bitmap largeIcon = null;
|
||||
if (np.hasPayload()) {
|
||||
int width = 64; // default icon dimensions
|
||||
int height = 64;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
|
||||
height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
|
||||
}
|
||||
final InputStream input = np.getPayload();
|
||||
largeIcon = BitmapFactory.decodeStream(np.getPayload());
|
||||
try { input.close(); } catch (Exception e) { }
|
||||
if (largeIcon != null) {
|
||||
//Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height);
|
||||
if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) {
|
||||
// older API levels don't scale notification icons automatically, therefore:
|
||||
largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(np.getString("appName"))
|
||||
.setContentText(np.getString("ticker"))
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(np.getString("ticker"))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setAutoCancel(true)
|
||||
.setLocalOnly(true) // to avoid bouncing the notification back to other kdeconnect nodes
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
try {
|
||||
// tag all incoming notifications
|
||||
notificationManager.notify("kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
|
||||
} catch (Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
}
|
||||
|
||||
}
|
@@ -24,7 +24,6 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.KeyExchange;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.Session;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
@@ -35,10 +34,10 @@ import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||
import org.apache.sshd.server.SshFile;
|
||||
import org.apache.sshd.server.command.ScpCommandFactory;
|
||||
import org.apache.sshd.server.kex.DHG1;
|
||||
import org.apache.sshd.server.kex.DHG14;
|
||||
import org.apache.sshd.server.filesystem.NativeFileSystemView;
|
||||
import org.apache.sshd.server.filesystem.NativeSshFile;
|
||||
import org.apache.sshd.server.kex.DHG1;
|
||||
import org.apache.sshd.server.kex.DHG14;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.sftp.SftpSubsystem;
|
||||
@@ -59,43 +58,6 @@ import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
public void setUser(String user) {this.user = user;}
|
||||
public String getUser() {return this.user;}
|
||||
|
||||
public void setPassword(String password) {this.password = password;}
|
||||
public String getPassword() {return this.password;}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, String password, ServerSession session) {
|
||||
return user.equals(this.user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
private String user;
|
||||
private String password;
|
||||
}
|
||||
|
||||
class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
|
||||
|
||||
private final List<PublicKey> keys = new ArrayList<>();
|
||||
|
||||
public void addKey(PublicKey key) {
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, PublicKey key, ServerSession session) {
|
||||
for (PublicKey k : keys) {
|
||||
if (key.equals(k)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SimpleSftpServer {
|
||||
private static final int STARTPORT = 1739;
|
||||
private static final int ENDPORT = 1764;
|
||||
@@ -109,7 +71,7 @@ class SimpleSftpServer {
|
||||
public final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
|
||||
|
||||
static {
|
||||
Security.insertProviderAt( SslHelper.BC, 1);
|
||||
Security.insertProviderAt(SslHelper.BC, 1);
|
||||
SecurityUtils.setRegisterBouncyCastle(false);
|
||||
}
|
||||
private final SshServer sshd = SshServer.setUpDefaultServer();
|
||||
@@ -129,9 +91,8 @@ class SimpleSftpServer {
|
||||
if (device.publicKey != null) {
|
||||
keyAuth.addKey(device.publicKey);
|
||||
sshd.setPublickeyAuthenticator(keyAuth);
|
||||
} else {
|
||||
sshd.setPasswordAuthenticator(passwordAuth);
|
||||
}
|
||||
sshd.setPasswordAuthenticator(passwordAuth);
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
@@ -192,20 +153,18 @@ class SimpleSftpServer {
|
||||
return ip6;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SecureFileSystemFactory implements FileSystemFactory {
|
||||
static class SecureFileSystemFactory implements FileSystemFactory {
|
||||
|
||||
public SecureFileSystemFactory() {}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public FileSystemView createFileSystemView(final Session username) {
|
||||
final String base = "/";
|
||||
return new SecureFileSystemView(base, username.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
class SecureFileSystemView extends NativeFileSystemView {
|
||||
static class SecureFileSystemView extends NativeFileSystemView {
|
||||
// the first and the last character will always be '/'
|
||||
// It is always with respect to the root directory.
|
||||
private String currDir = "/";
|
||||
@@ -241,8 +200,47 @@ class SimpleSftpServer {
|
||||
}
|
||||
}
|
||||
|
||||
class SecureSshFile extends NativeSshFile {
|
||||
static class SecureSshFile extends NativeSshFile {
|
||||
public SecureSshFile(final String fileName, final File file, final String userName) {
|
||||
super(fileName, file, userName);
|
||||
}
|
||||
}
|
||||
|
||||
static class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
public void setUser(String user) {this.user = user;}
|
||||
public String getUser() {return this.user;}
|
||||
|
||||
public void setPassword(String password) {this.password = password;}
|
||||
public String getPassword() {return this.password;}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, String password, ServerSession session) {
|
||||
return user.equals(this.user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
private String user;
|
||||
private String password;
|
||||
}
|
||||
|
||||
static class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
|
||||
|
||||
private final List<PublicKey> keys = new ArrayList<>();
|
||||
|
||||
public void addKey(PublicKey key) {
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, PublicKey key, ServerSession session) {
|
||||
for (PublicKey k : keys) {
|
||||
if (key.equals(k)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -21,45 +21,18 @@
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.List.EntryItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
public class SendFileActivity extends ActionBarActivity {
|
||||
|
@@ -170,7 +170,7 @@ public class ShareActivity extends ActionBarActivity {
|
||||
} catch (Exception e) {
|
||||
isUrl = false;
|
||||
}
|
||||
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE);
|
||||
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE_REQUEST);
|
||||
if (isUrl) {
|
||||
np.set("url", text);
|
||||
} else {
|
||||
|
@@ -58,7 +58,7 @@ import java.util.ArrayList;
|
||||
|
||||
public class SharePlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
//public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
||||
|
||||
final static boolean openUrlsDirectly = true;
|
||||
|
@@ -20,20 +20,14 @@
|
||||
|
||||
package org.kde.kdeconnect.Plugins.TelepathyPlugin;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static android.provider.ContactsContract.CommonDataKinds;
|
||||
import static android.provider.ContactsContract.Contacts;
|
||||
|
||||
public class TelepathyPlugin extends Plugin {
|
||||
|
||||
|
||||
@@ -174,7 +168,7 @@ public class TelepathyPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_SMS_REQUEST};
|
||||
return new String[]{PACKAGE_TYPE_SMS_REQUEST, TelephonyPlugin.PACKAGE_TYPE_TELEPHONY_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -116,7 +116,18 @@ public class TelephonyPlugin extends Plugin {
|
||||
}
|
||||
|
||||
if (contactInfo.containsKey("photoID")) {
|
||||
np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
|
||||
String photoUri = contactInfo.get("photoID");
|
||||
if (photoUri != null) {
|
||||
try {
|
||||
String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
|
||||
if (base64photo != null && !base64photo.isEmpty()) {
|
||||
np.set("phoneThumbnail", base64photo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("TelephonyPlugin", "Failed to get contact photo");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
|
@@ -22,7 +22,6 @@ package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -42,17 +41,16 @@ import android.widget.TextView;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.UserInterface.List.PluginItem;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.List.CustomItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.List.PluginItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.SmallEntryItem;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@@ -259,8 +257,8 @@ public class DeviceFragment extends Fragment {
|
||||
if (device.certificate == null) {
|
||||
builder.setMessage(R.string.encryption_info_msg_no_ssl);
|
||||
} else {
|
||||
builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n " + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n"
|
||||
+ context.getResources().getString(R.string.remote_device_fingerprint) + "\n " + SslHelper.getCertificateHash(device.certificate));
|
||||
builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n" + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n"
|
||||
+ context.getResources().getString(R.string.remote_device_fingerprint) + "\n" + SslHelper.getCertificateHash(device.certificate));
|
||||
}
|
||||
builder.create().show();
|
||||
return true;
|
||||
|
@@ -36,8 +36,6 @@ public class PairingDeviceItem implements ListAdapter.Item {
|
||||
|
||||
private final Callback callback;
|
||||
private final Device device;
|
||||
private TextView titleView;
|
||||
private ImageView icon;
|
||||
|
||||
public PairingDeviceItem(Device device, Callback callback) {
|
||||
this.device = device;
|
||||
@@ -52,10 +50,10 @@ public class PairingDeviceItem implements ListAdapter.Item {
|
||||
public View inflateView(LayoutInflater layoutInflater) {
|
||||
final View v = layoutInflater.inflate(R.layout.list_item_with_icon_entry, null);
|
||||
|
||||
icon = (ImageView)v.findViewById(R.id.list_item_entry_icon);
|
||||
ImageView icon = (ImageView) v.findViewById(R.id.list_item_entry_icon);
|
||||
icon.setImageDrawable(device.getIcon());
|
||||
|
||||
titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
|
||||
TextView titleView = (TextView) v.findViewById(R.id.list_item_entry_title);
|
||||
titleView.setText(device.getName());
|
||||
|
||||
if (device.compareProtocolVersion() != 0) {
|
||||
@@ -65,7 +63,7 @@ public class PairingDeviceItem implements ListAdapter.Item {
|
||||
summaryView.setText(R.string.protocol_version_newer);
|
||||
summaryView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
//FIXME: Uncoment
|
||||
//FIXME: Uncoment when we decide old versions are old enough to notify the user.
|
||||
summaryView.setVisibility(View.GONE);
|
||||
/*
|
||||
summaryView.setText(R.string.protocol_version_older);
|
||||
|
@@ -20,7 +20,6 @@
|
||||
|
||||
package org.kde.kdeconnect.UserInterface.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@@ -40,8 +40,6 @@ public class MaterialActivity extends AppCompatActivity {
|
||||
|
||||
private NavigationView mNavigationView;
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private ActionBarDrawerToggle mDrawerToggle;
|
||||
private View mDrawerHeader;
|
||||
|
||||
private String mCurrentDevice;
|
||||
|
||||
@@ -55,14 +53,14 @@ public class MaterialActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mNavigationView = (NavigationView) findViewById(R.id.navigation_drawer);
|
||||
mDrawerHeader = mNavigationView.getHeaderView(0);
|
||||
View mDrawerHeader = mNavigationView.getHeaderView(0);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
|
||||
ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.string.open, /* "open drawer" description */
|
||||
R.string.close /* "close drawer" description */
|
||||
|
@@ -25,7 +25,6 @@ import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -37,8 +36,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
|
@@ -8,7 +8,6 @@ import android.widget.TextView;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class PluginPreference extends CheckBoxPreference {
|
||||
|
@@ -22,7 +22,6 @@ package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
|
@@ -26,17 +26,19 @@ import android.test.AndroidTestCase;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler;
|
||||
import org.mockito.Mockito;
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.spongycastle.asn1.x500.X500NameBuilder;
|
||||
import org.spongycastle.asn1.x500.style.BCStyle;
|
||||
import org.spongycastle.cert.X509v3CertificateBuilder;
|
||||
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.operator.ContentSigner;
|
||||
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
@@ -133,6 +135,7 @@ public class DeviceTest extends AndroidTestCase {
|
||||
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
||||
LanLink link = Mockito.mock(LanLink.class);
|
||||
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
||||
Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class));
|
||||
Device device = new Device(getContext(), fakeNetworkPackage, link);
|
||||
|
||||
KeyPair keyPair;
|
||||
@@ -213,7 +216,7 @@ public class DeviceTest extends AndroidTestCase {
|
||||
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initialiseCertificate", "Exception");
|
||||
Log.e("KDE/initialiseCert", "Exception");
|
||||
}
|
||||
|
||||
NetworkPackage fakeNetworkPackage = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
|
||||
@@ -226,6 +229,7 @@ public class DeviceTest extends AndroidTestCase {
|
||||
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
||||
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
||||
LanLink link = Mockito.mock(LanLink.class);
|
||||
Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class));
|
||||
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
||||
Device device = new Device(getContext(), fakeNetworkPackage, link);
|
||||
device.publicKey = keyPair.getPublic();
|
||||
|
@@ -20,27 +20,19 @@
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class LanLinkProviderTest extends AndroidTestCase {
|
||||
|
||||
private NioSocketAcceptor tcpAcceptor = null;
|
||||
private NioDatagramAcceptor udpAcceptor = null;
|
||||
private LanLinkProvider linkProvider;
|
||||
|
||||
@Override
|
||||
@@ -50,178 +42,35 @@ public class LanLinkProviderTest extends AndroidTestCase {
|
||||
System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath());
|
||||
|
||||
linkProvider = new LanLinkProvider(getContext());
|
||||
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("tcpAcceptor");
|
||||
field.setAccessible(true);
|
||||
tcpAcceptor = (NioSocketAcceptor)field.get(linkProvider);
|
||||
assertNotNull(tcpAcceptor);
|
||||
}catch (Exception e){
|
||||
fail("Error getting tcpAcceptor from LanLinkProvider");
|
||||
}
|
||||
|
||||
try{
|
||||
Field field = LanLinkProvider.class.getDeclaredField("udpAcceptor");
|
||||
field.setAccessible(true);
|
||||
udpAcceptor = (NioDatagramAcceptor)field.get(linkProvider);
|
||||
assertNotNull(udpAcceptor);
|
||||
}catch (Exception e){
|
||||
fail("Error getting udp acceptor from LanLinkProvider");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
tcpAcceptor.dispose();
|
||||
udpAcceptor.dispose();
|
||||
}
|
||||
|
||||
public void testTcpAcceptor(){
|
||||
|
||||
assertNotNull(tcpAcceptor.getHandler());
|
||||
assertEquals(tcpAcceptor.getSessionConfig().isKeepAlive(), true);
|
||||
assertEquals(tcpAcceptor.getSessionConfig().isReuseAddress(), true);
|
||||
assertNotNull(tcpAcceptor.getFilterChain().get("codec"));
|
||||
|
||||
}
|
||||
|
||||
public void testUdpAcceptor(){
|
||||
|
||||
assertNull(udpAcceptor.getHandler());
|
||||
assertEquals(udpAcceptor.getSessionConfig().isReuseAddress(), true);
|
||||
assertNotNull(udpAcceptor.getFilterChain().get("codec"));
|
||||
}
|
||||
|
||||
public void testOnStart() throws Exception{
|
||||
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
Mockito.when(session.getRemoteAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
|
||||
linkProvider.onStart();
|
||||
|
||||
assertNotNull(udpAcceptor.getHandler());
|
||||
assertEquals(udpAcceptor.getLocalAddress().getPort(), 1714);
|
||||
}
|
||||
|
||||
public void testUdpPackageReceived() throws Exception {
|
||||
|
||||
final int port = 5000;
|
||||
public void testIdentityPackageReceived() throws Exception{
|
||||
|
||||
NetworkPackage networkPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(networkPackage.getType()).thenReturn("kdeconnect.identity");
|
||||
Mockito.when(networkPackage.getString("deviceId")).thenReturn("testDevice");
|
||||
Mockito.when(networkPackage.getString("deviceName")).thenReturn("Test Device");
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(NetworkPackage.ProtocolVersion);
|
||||
Mockito.when(networkPackage.getString("deviceType")).thenReturn("phone");
|
||||
Mockito.when(networkPackage.getInt("tcpPort")).thenReturn(port);
|
||||
|
||||
final String serialized = "{\"type\":\"kdeconnect.identity\",\"id\":12345,\"body\":{\"deviceName\":\"Test Device\",\"deviceType\":\"phone\",\"deviceId\":\"testDevice\",\"protocolVersion\":5,\"tcpPort\": "+ port +"}}";
|
||||
Mockito.when(networkPackage.serialize()).thenReturn(serialized);
|
||||
|
||||
// Mocking udp session
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
Mockito.when(session.getRemoteAddress()).thenReturn(new InetSocketAddress(port));
|
||||
|
||||
|
||||
// Making a server socket, so that original LanLinkProvider can connect to it when it receives our fake package
|
||||
final ServerSocket serverSocket = new ServerSocket();
|
||||
serverSocket.bind(new InetSocketAddress(port));
|
||||
|
||||
final Thread thread = new Thread (new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
while (true) {
|
||||
if (inputStream.available() != 0) {
|
||||
// Data received from socket should be an identity package
|
||||
byte[] inputData = new byte[inputStream.available()];
|
||||
inputStream.read(inputData);
|
||||
|
||||
NetworkPackage receivedPackage = NetworkPackage.unserialize(new String(inputData));
|
||||
NetworkPackage identityPackage = NetworkPackage.createIdentityPackage(getContext());
|
||||
|
||||
// If any assertion fails, its output will be in logcat, not on test case thread anymore
|
||||
assertEquals(receivedPackage.getType(), identityPackage.getType());
|
||||
assertEquals(receivedPackage.getString("deviceName"), identityPackage.getString("deviceName"));
|
||||
assertEquals(receivedPackage.getString("deviceId"), identityPackage.getString("deviceId"));
|
||||
assertEquals(receivedPackage.getInt("protocolVersion"), identityPackage.getInt("protocolVersion"));
|
||||
|
||||
serverSocket.close();
|
||||
// Socket not closed to ensure visibleComputers contains entry for testDevice
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}catch (Exception e){
|
||||
assertEquals("Exception in thread",1,5);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
thread.start();
|
||||
linkProvider.onStart();
|
||||
udpAcceptor.getHandler().messageReceived(session, networkPackage.serialize());
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Wait 1 secs for our server, and then end test
|
||||
thread.join(1 * 1000);
|
||||
|
||||
// visibleComputers should contain an entry for testDevice
|
||||
HashMap<String, LanLink> visibleComputers;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("visibleComputers");
|
||||
field.setAccessible(true);
|
||||
visibleComputers = (HashMap<String, LanLink>)field.get(linkProvider);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNotNull(visibleComputers.get("testDevice"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testTcpIdentityPackageReceived() throws Exception{
|
||||
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
|
||||
NetworkPackage networkPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(networkPackage.getType()).thenReturn("kdeconnect.identity");
|
||||
Mockito.when(networkPackage.getString("deviceId")).thenReturn("testDevice");
|
||||
Mockito.when(networkPackage.getString("deviceName")).thenReturn("Test Device");
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(NetworkPackage.ProtocolVersion);
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(5);
|
||||
Mockito.when(networkPackage.getString("deviceType")).thenReturn("phone");
|
||||
|
||||
String serialized = "{\"type\":\"kdeconnect.identity\",\"id\":12345,\"body\":{\"deviceName\":\"Test Device\",\"deviceType\":\"phone\",\"deviceId\":\"testDevice\",\"protocolVersion\":5}}";
|
||||
Mockito.when(networkPackage.serialize()).thenReturn(serialized);
|
||||
|
||||
Socket channel = Mockito.mock(Socket.class);
|
||||
try {
|
||||
tcpAcceptor.getHandler().messageReceived(session, networkPackage.serialize());
|
||||
Method method = LanLinkProvider.class.getDeclaredMethod("identityPackageReceived", NetworkPackage.class, Socket.class, LanLink.ConnectionStarted.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(linkProvider, networkPackage, channel, LanLink.ConnectionStarted.Locally);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
|
||||
LongSparseArray<LanLink> nioSessions;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("nioSessions");
|
||||
field.setAccessible(true);
|
||||
nioSessions = (LongSparseArray<LanLink>)field.get(linkProvider);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNotNull(nioSessions.get(12345l));
|
||||
|
||||
|
||||
HashMap<String, LanLink> visibleComputers;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("visibleComputers");
|
||||
@@ -232,14 +81,5 @@ public class LanLinkProviderTest extends AndroidTestCase {
|
||||
}
|
||||
assertNotNull(visibleComputers.get("testDevice"));
|
||||
|
||||
|
||||
// Testing session closed
|
||||
try {
|
||||
tcpAcceptor.getHandler().sessionClosed(session);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNull(nioSessions.get(12345l));
|
||||
assertNull(visibleComputers.get("testDevice"));
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ package org.kde.kdeconnect;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.mockito.Mockito;
|
||||
@@ -35,19 +35,19 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
|
||||
public class LanLinkTest extends AndroidTestCase {
|
||||
|
||||
LanLink lanLink;
|
||||
Channel channel;
|
||||
Device.SendPackageStatusCallback callback;
|
||||
LanLink badLanLink;
|
||||
LanLink goodLanLink;
|
||||
|
||||
ChannelFuture channelFutureSuccess, channelFutureFailure;
|
||||
OutputStream badOutputStream;
|
||||
OutputStream goodOutputStream;
|
||||
|
||||
Device.SendPackageStatusCallback callback;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -58,35 +58,23 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
||||
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
||||
|
||||
channel = Mockito.mock(Channel.class);
|
||||
// Mockito.when(channel.hashCode()).thenReturn(12345);
|
||||
Mockito.when(channel.remoteAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
// Mockito.doReturn(new InetSocketAddress(5000)).when(channel).remoteAddress();
|
||||
|
||||
callback = Mockito.mock(Device.SendPackageStatusCallback.class);
|
||||
Mockito.doNothing().when(callback).sendSuccess();
|
||||
Mockito.doNothing().when(callback).sendProgress(Mockito.any(Integer.class));
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
throw (Throwable) invocationOnMock.getArguments()[0];
|
||||
}
|
||||
}).when(callback).sendFailure(Mockito.any(Throwable.class));
|
||||
|
||||
channelFutureSuccess = Mockito.mock(ChannelFuture.class);
|
||||
Mockito.when(channelFutureSuccess.isDone()).thenReturn(true);
|
||||
Mockito.when(channelFutureSuccess.isSuccess()).thenReturn(true);
|
||||
Mockito.when(channelFutureSuccess.channel()).thenReturn(channel);
|
||||
Mockito.when(channelFutureSuccess.sync()).thenReturn(channelFutureSuccess);
|
||||
goodOutputStream = Mockito.mock(OutputStream.class);
|
||||
badOutputStream = Mockito.mock(OutputStream.class);
|
||||
Mockito.doThrow(new IOException("AAA")).when(badOutputStream).write(Mockito.any(byte[].class));
|
||||
|
||||
channelFutureFailure = Mockito.mock(ChannelFuture.class);
|
||||
Mockito.when(channelFutureFailure.isDone()).thenReturn(true);
|
||||
Mockito.when(channelFutureFailure.isSuccess()).thenReturn(false);
|
||||
Mockito.when(channelFutureFailure.cause()).thenReturn(new IOException("Cannot send package"));
|
||||
Mockito.when(channelFutureFailure.channel()).thenReturn(channel);
|
||||
Mockito.when(channelFutureFailure.sync()).thenReturn(channelFutureFailure);
|
||||
|
||||
lanLink = new LanLink(getContext(), channel, "testDevice", linkProvider, BaseLink.ConnectionStarted.Remotely);
|
||||
Socket socketMock = Mockito.mock(Socket.class);
|
||||
Mockito.when(socketMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
Mockito.when(socketMock.getOutputStream()).thenReturn(goodOutputStream);
|
||||
|
||||
Socket socketBadMock = Mockito.mock(Socket.class);
|
||||
Mockito.when(socketBadMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
Mockito.when(socketBadMock.getOutputStream()).thenReturn(badOutputStream);
|
||||
|
||||
goodLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketMock, LanLink.ConnectionStarted.Remotely);
|
||||
badLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketBadMock, LanLink.ConnectionStarted.Remotely);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,7 +82,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testSendPackageSuccess(){
|
||||
public void testSendPackageSuccess() throws JSONException {
|
||||
|
||||
NetworkPackage testPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(testPackage.getType()).thenReturn("kdeconnect.test");
|
||||
@@ -102,18 +90,12 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
Mockito.when(testPackage.getString("testName")).thenReturn("testSendPackageSuccess");
|
||||
Mockito.when(testPackage.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPackageSuccess\"}}");
|
||||
|
||||
try {
|
||||
Mockito.when(channel.writeAndFlush(testPackage.serialize())).thenReturn(channelFutureSuccess);
|
||||
lanLink.sendPackage(testPackage, callback);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
goodLanLink.sendPackage(testPackage, callback);
|
||||
|
||||
assertEquals(channelFutureSuccess.isDone(), true);
|
||||
assertEquals(channelFutureSuccess.isSuccess(), true);
|
||||
Mockito.verify(callback).sendSuccess();
|
||||
}
|
||||
|
||||
public void testSendPackageFail(){
|
||||
public void testSendPackageFail() throws JSONException {
|
||||
|
||||
NetworkPackage testPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(testPackage.getType()).thenReturn("kdeconnect.test");
|
||||
@@ -121,16 +103,10 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
Mockito.when(testPackage.getString("testName")).thenReturn("testSendPackageFail");
|
||||
Mockito.when(testPackage.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPackageFail\"}}");
|
||||
|
||||
try {
|
||||
Mockito.when(channel.writeAndFlush(testPackage.serialize())).thenReturn(channelFutureFailure);
|
||||
lanLink.sendPackage(testPackage, callback);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
badLanLink.sendPackage(testPackage, callback);
|
||||
|
||||
Mockito.verify(callback).sendFailure(Mockito.any(RuntimeException.class));
|
||||
|
||||
assertEquals(channelFutureFailure.isDone(), true);
|
||||
assertEquals(channelFutureFailure.isSuccess(), false);
|
||||
assertEquals(channelFutureFailure.cause() instanceof IOException, true );
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +133,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
try {
|
||||
socket = new Socket();
|
||||
int tcpPort = np.getPayloadTransferInfo().getInt("port");
|
||||
InetSocketAddress address = (InetSocketAddress)channel.remoteAddress();
|
||||
InetSocketAddress address = new InetSocketAddress(5000);
|
||||
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
np.setPayload(socket.getInputStream(), np.getPayloadSize());
|
||||
} catch (Exception e) {
|
||||
@@ -252,17 +228,18 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
|
||||
String stringNetworkPackage = (String)invocationOnMock.getArguments()[0];
|
||||
Log.e("LanLinkTest","Write to stream");
|
||||
String stringNetworkPackage = new String((byte[])invocationOnMock.getArguments()[0]);
|
||||
final NetworkPackage np = NetworkPackage.unserialize(stringNetworkPackage);
|
||||
|
||||
downloader.setNetworkPackage(np);
|
||||
downloader.start();
|
||||
|
||||
return channelFutureSuccess;
|
||||
return stringNetworkPackage.length();
|
||||
}
|
||||
}).when(channel).writeAndFlush(Mockito.anyString());
|
||||
}).when(goodOutputStream).write(Mockito.any(byte[].class));
|
||||
|
||||
lanLink.sendPackage(sharePackage, callback);
|
||||
goodLanLink.sendPackage(sharePackage, callback);
|
||||
|
||||
try {
|
||||
// Wait 1 secs for downloader to finish (if some error, it will continue and assert will fail)
|
||||
@@ -273,5 +250,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
}
|
||||
assertEquals(new String(data), new String(downloader.getOutputStream().toByteArray()));
|
||||
|
||||
Mockito.verify(callback).sendSuccess();
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -124,15 +125,13 @@ public class NetworkPackageTest extends AndroidTestCase{
|
||||
assertEquals(decrypted.getType(), copy.getType());
|
||||
assertEquals(decrypted.getJSONArray("body"), copy.getJSONArray("body"));
|
||||
|
||||
String json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":\"A really long package id\",\"payloadSize\":0,\"payloadTransferInfo\":{},\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n";
|
||||
String json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":945945945,\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n";
|
||||
NetworkPackage longJsonNp = NetworkPackage.unserialize(json);
|
||||
try {
|
||||
NetworkPackage encrypted = RsaHelper.encrypt(longJsonNp, publicKey);
|
||||
decrypted = RsaHelper.decrypt(encrypted, privateKey);
|
||||
|
||||
String decryptedJson = decrypted.serialize();
|
||||
assertEquals(json, decryptedJson);
|
||||
|
||||
JSONAssert.assertEquals(json, decryptedJson, true);
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
Reference in New Issue
Block a user