2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-01 06:35:09 +00:00

Compare commits

..

39 Commits
v1.0.2 ... v1.1

Author SHA1 Message Date
Albert Vaca
163e3c31f4 Bumped version number to release 2016-06-21 17:29:26 +02:00
Albert Vaca
5a5e236710 Update build tools 2016-06-21 17:29:10 +02:00
Albert Vaca
603c87d42d Fixed tests after last refactor of lanlink 2016-06-21 16:45:18 +02:00
Albert Vaca
1a04bfbbea Fixed simple issues detected by lint 2016-06-21 16:44:21 +02:00
Albert Vaca
fec0b34330 Socket can't be null 2016-06-21 13:39:55 +02:00
Albert Vaca
f6c4084746 error -> warning for some logs 2016-06-21 13:38:21 +02:00
Albert Vaca
968d018f41 We were never reusing the existing link 2016-06-21 13:37:49 +02:00
Albert Vaca
4fc6ca8d4f Further simplified lanbackend 2016-06-20 14:26:49 +02:00
Albert Vaca
d3ab18b721 Increased version number to release as beta 2016-06-17 10:03:54 +02:00
Albert Vaca
b2d0e57641 Missing start() call on a thread! 2016-06-17 10:02:22 +02:00
Albert Vaca
66238406d6 Print if trusted when creating an ssl socket 2016-06-17 09:59:46 +02:00
Albert Vaca
010c960680 Check for trusted only once 2016-06-17 09:59:31 +02:00
Albert Vaca
49d4383828 Renamed running -> listening 2016-06-17 09:56:53 +02:00
Albert Vaca
226869e200 Fixed shared urls and text having the wrong package type 2016-06-17 09:43:23 +02:00
Albert Vaca
bbc1113710 Bumped version to release in beta channel 2016-06-17 02:27:18 +02:00
Albert Vaca
b3bacf241c Support both the new and old UDP port 2016-06-17 02:25:52 +02:00
Albert Vaca
ff47313409 Be less verbose 2016-06-17 02:25:10 +02:00
Albert Vaca
ac4f072322 Code cleanup 2016-06-17 02:01:20 +02:00
Albert Vaca
5ed1b80716 Fast compilation 2016-06-16 23:48:16 +02:00
Albert Vaca
8a413bb42e Working :D 2016-06-16 23:48:16 +02:00
Albert Vaca
71dc713578 Getting rid of netty WIP 2016-06-16 23:48:16 +02:00
Albert Vaca
ca3d677db6 Fixed warning 2016-06-16 23:46:24 +02:00
Albert Vaca
79ed37345b Simplified logic to find open ports. 2016-06-16 23:10:08 +02:00
Pinak Ahuja
24c404400f Add support to deal with album arts sent by mpris plugin.
We should probably look into resizing the album art according
 to the DPI of the device.
2016-06-16 10:37:04 +02:00
l10n daemon script
082de423c0 SVN_SILENT made messages (after extraction) 2016-06-16 07:48:15 +00:00
Albert Vaca
b5fb7f73ee Updated netty 2016-06-16 00:53:36 +02:00
Albert Vaca
097d1f5fa5 Removed log 2016-06-16 00:40:37 +02:00
Albert Vaca
5fa43f8979 Removed SO_BACKLOG because of a log message about it being unsupported 2016-06-16 00:40:37 +02:00
Albert Vaca
e5c7adba3a Ignore udp packets on the old port for devices that know about the new port 2016-06-16 00:40:20 +02:00
Kai Uwe Broulik
ced5c71369 [MousePadPlugin] Support mouse wheel events
This allows to scroll using a touch-enabled physical keyboard or a real mouse

BUG: 360006

Reviewed-By: Albert Vaca
2016-06-14 18:42:21 +02:00
Albert Vaca
9bf2adefc4 Commented annoying log message 2016-06-14 17:39:41 +02:00
Albert Vaca
24685348cf Moved hardcoded protocol version numbers to constants 2016-06-14 17:39:20 +02:00
Albert Vaca
7556e1d7fa Allow kdeconnect to broadcast both on UDP port 1714 and 1716 2016-06-14 11:13:20 +02:00
Albert Vaca
c9b852f88c Fix leak on Android < IceCream when workerGroup hasn't been initialized 2016-06-14 11:13:20 +02:00
Albert Vaca
d8169f3787 Reuse channel initializers in LanLinkProvider 2016-06-14 11:13:20 +02:00
l10n daemon script
b71e8562bc SVN_SILENT made messages (after extraction) 2016-06-13 08:19:24 +00:00
Albert Vaca
4d19a7cdc8 Fixed tests 2016-06-12 21:07:01 +02:00
Albert Vaca
ae7a80e262 Removed duplicate code when receiving identity packages via tcp and udp 2016-06-12 21:00:19 +02:00
l10n daemon script
4a269a607c SVN_SILENT made messages (after extraction) 2016-06-12 07:23:58 +00:00
29 changed files with 812 additions and 867 deletions

View File

@@ -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="1020"
android:versionName="1.0.2">
android:versionCode="1102"
android:versionName="1.1">
<uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="22" />

View File

@@ -10,15 +10,16 @@ buildscript {
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,6 +53,10 @@ 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
@@ -61,22 +66,22 @@ android {
}
dependencies {
repositories {
mavenCentral()
}
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 'com.madgag.spongycastle:pkix:1.54.0.0'
compile 'io.netty:netty-handler:4.1.0.Final'
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')
}

View File

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

View File

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

View File

@@ -65,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>
@@ -150,6 +151,7 @@
<string name="pref_plugin_telepathy">Poslat SMS</string>
<string name="pref_plugin_telepathy_desc">Posílejte zprávy ze své pracovní plochy</string>
<string name="plugin_not_supported">Tento modul zařízení nepodporuje</string>
<string name="findmyphone_title">Najít můj telefon</string>
<string name="findmyphone_description">Prozvoní tento telefon, takže jej můžete najít.</string>
<string name="findmyphone_found">Nalezeno</string>
<string name="open">Otevřít</string>

View File

@@ -13,6 +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ø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>
@@ -31,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>
@@ -38,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>
@@ -62,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>
@@ -147,6 +155,7 @@
<string name="pref_plugin_telepathy">Send SMS</string>
<string name="pref_plugin_telepathy_desc">Send SMS-beskeder fra din desktop</string>
<string name="plugin_not_supported">Dette plugin er ikke understøttet af enheden</string>
<string name="findmyphone_title">Find min telefon</string>
<string name="findmyphone_description">Ringer til denne telefon, så du kan finde den.</string>
<string name="findmyphone_found">Fundet</string>
<string name="open">Åbn</string>

163
res/values-el/strings.xml Normal file
View 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>

View File

@@ -13,6 +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">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>
@@ -65,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>
@@ -150,6 +155,7 @@
<string name="pref_plugin_telepathy">Enviar SMS</string>
<string name="pref_plugin_telepathy_desc">Enviar mensajes de texto desde el escritorio</string>
<string name="plugin_not_supported">Este complemento no está permitido por el dispositivo</string>
<string name="findmyphone_title">Encontrar mi teléfono</string>
<string name="findmyphone_description">Hace sonar este teléfono para que pueda encontrarlo.</string>
<string name="findmyphone_found">Encontrado</string>
<string name="open">Abrir</string>

View File

@@ -68,6 +68,8 @@
<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>

View File

@@ -21,74 +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;
this.onSsl = onSsl;
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);
}
@@ -102,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;
@@ -129,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);
@@ -142,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) { }
}
}
@@ -215,7 +255,7 @@ public class LanLink extends BaseLink {
sendPackageInternal(np, callback, key);
}
public void injectNetworkPackage(NetworkPackage np) {
private void receivedNetworkPackage(NetworkPackage np) {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
try {
@@ -224,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");
}
@@ -254,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() {
@@ -318,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;
}

View File

@@ -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,440 +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.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 bootstrap = new Bootstrap();
bootstrap.group(clientGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new TcpInitializer());
int tcpPort = identityPackage.getInt("tcpPort", port);
final ChannelFuture channelFuture = bootstrap.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.childOption(ChannelOption.SO_BACKLOG, 100);
tcpBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
tcpBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
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);
@@ -473,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");
@@ -491,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 + ")");
@@ -508,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();
}
}
@@ -534,6 +445,4 @@ public class LanLinkProvider extends BaseLinkProvider {
return "LanLinkProvider";
}
}

View File

@@ -63,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();
}
@@ -161,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() {

View File

@@ -36,6 +36,7 @@ import android.util.Log;
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;
@@ -70,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<>();
@@ -571,7 +574,7 @@ public class Device implements BaseLink.PackageReceiver {
}
}
} 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 {
@@ -639,9 +642,9 @@ public class Device implements BaseLink.PackageReceiver {
hackToMakeRetrocompatiblePacketTypes(np);
if (protocolVersion >= 6 && !supportedOutgoingInterfaces.contains(np.getType()) && !NetworkPackage.protocolPackageTypes.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...");
@@ -652,7 +655,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) {
@@ -786,7 +789,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) {
@@ -868,7 +871,7 @@ public class Device implements BaseLink.PackageReceiver {
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));
@@ -906,6 +909,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;
@@ -915,12 +925,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","");
}

View File

@@ -20,9 +20,11 @@
package org.kde.kdeconnect.Helpers;
import android.annotation.TargetApi;
import android.content.Context;
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;
@@ -36,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,27 +86,28 @@ public class ContactsHelper {
public static String photoId64Encoded(Context context, String photoId) {
if (photoId == null) {
return new String();
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) { };
}
}
}

View File

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

View File

@@ -41,6 +41,7 @@ import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
@@ -49,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;
@@ -131,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);
@@ -140,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);
}
@@ -195,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) {
@@ -252,7 +246,6 @@ public class SslHelper {
} catch (Exception e) {
return null;
}
}
public static Certificate parseCertificate(byte[] certificateBytes) throws IOException, CertificateException {

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

View File

@@ -36,7 +36,7 @@ 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";

View File

@@ -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;
@@ -238,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
@@ -250,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;
}
@@ -387,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);

View File

@@ -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,6 +34,7 @@ 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;
@@ -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())) {

View File

@@ -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 {
@@ -208,6 +218,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 +241,8 @@ public class MprisPlugin extends Plugin {
return currentSong;
}
public Bitmap getCurrentArt() { return currentArt; }
public String getPlayer() {
return player;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,17 +26,19 @@ import android.test.AndroidTestCase;
import android.util.Base64;
import android.util.Log;
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.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.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();

View File

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

View File

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

View File

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