mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-01 14:45:08 +00:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
163e3c31f4 | ||
|
5a5e236710 | ||
|
603c87d42d | ||
|
1a04bfbbea | ||
|
fec0b34330 | ||
|
f6c4084746 | ||
|
968d018f41 | ||
|
4fc6ca8d4f | ||
|
d3ab18b721 | ||
|
b2d0e57641 | ||
|
66238406d6 | ||
|
010c960680 | ||
|
49d4383828 | ||
|
226869e200 | ||
|
bbc1113710 | ||
|
b3bacf241c | ||
|
ff47313409 | ||
|
ac4f072322 | ||
|
5ed1b80716 | ||
|
8a413bb42e | ||
|
71dc713578 | ||
|
ca3d677db6 | ||
|
79ed37345b | ||
|
24c404400f | ||
|
082de423c0 | ||
|
b5fb7f73ee | ||
|
097d1f5fa5 | ||
|
5fa43f8979 | ||
|
e5c7adba3a | ||
|
ced5c71369 | ||
|
9bf2adefc4 | ||
|
24685348cf | ||
|
7556e1d7fa | ||
|
c9b852f88c | ||
|
d8169f3787 | ||
|
b71e8562bc | ||
|
4d19a7cdc8 | ||
|
ae7a80e262 | ||
|
4a269a607c |
@@ -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" />
|
||||
|
23
build.gradle
23
build.gradle
@@ -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')
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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>
|
||||
|
@@ -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
163
res/values-el/strings.xml
Normal file
@@ -0,0 +1,163 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="pref_plugin_telephony">Ειδοποιήσεις τηλεφωνίας</string>
|
||||
<string name="pref_plugin_telephony_desc">Αποστολή ειδοποιήσεων για SMS και κλήσεις</string>
|
||||
<string name="pref_plugin_battery">Αναφορά μπαταρίας</string>
|
||||
<string name="pref_plugin_battery_desc">Περιοδική αναφορά κατάστασης μπαταρίας</string>
|
||||
<string name="pref_plugin_sftp">Αποκάλυψη συστήματος αρχείων</string>
|
||||
<string name="pref_plugin_sftp_desc">Επιτρέπει την απομακρυσμένη περιήγηση του συστήματος αρχείων του κινητού</string>
|
||||
<string name="pref_plugin_clipboard">Συγχρονισμός προχείρου</string>
|
||||
<string name="pref_plugin_clipboard_desc">Διαμοιρασμός περιεχομένου προχείρου</string>
|
||||
<string name="pref_plugin_mousepad">Απομακρυσμένη είσοδος στοιχείων</string>
|
||||
<string name="pref_plugin_mousepad_desc">Χρήση του κινητού ως ποντίκι και πληκτρολόγιο</string>
|
||||
<string name="pref_plugin_mpris">Κονσόλα πολυμέσων</string>
|
||||
<string name="pref_plugin_mpris_desc">Έλεγχος μουσικής/βίντεο από το κινητό</string>
|
||||
<string name="pref_plugin_runcommand">Εκτέλεση εντολής</string>
|
||||
<string name="pref_plugin_runcommand_desc">Εκτέλεση απομακρυσμένων εντολών από το κινητό</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Αποστολή και λήψη pings</string>
|
||||
<string name="pref_plugin_notifications">Συγχρονισμός ειδοποιήσεων</string>
|
||||
<string name="pref_plugin_notifications_desc">Πρόσβαση σε ειδοποιήσεις από άλλες συσκευές</string>
|
||||
<string name="pref_plugin_sharereceiver">Διαμοιρασμός και λήψη</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Διαμοιρασμός αρχείων και URL μεταξύ συσκευών</string>
|
||||
<string name="plugin_not_available">Αυτή η λειτουργία δεν είναι διαθέσιμη στην τρέχουσα έκδοση του Android</string>
|
||||
<string name="device_list_empty">Χωρίς συσκευές</string>
|
||||
<string name="ok">Εντάξει</string>
|
||||
<string name="cancel">Ακύρωση</string>
|
||||
<string name="open_settings">Ρυθμίσεις ανοίγματος</string>
|
||||
<string name="no_permissions">Απαιτείται παραχώρηση δικαιωμάτων για την πρόσβαση σε ειδοποιήσεις</string>
|
||||
<string name="send_ping">Αποστολή ping</string>
|
||||
<string name="open_mpris_controls">Έλεγχος πολυμέσων</string>
|
||||
<string name="open_mousepad">Απομακρυσμένη είσοδος στοιχείων</string>
|
||||
<string name="mousepad_info">Μετακινείστε το δάκτυλο στην οθόνη για να μετακινηθεί ο δρομέας του ποντικιού. Χτυπήστε για κλικ και χρησιμοποιήστε δύο/τρία δάκτυλα για δεξί και μεσαίο κλικ. Πιέστε με διάρκεια για μετακίνηση και απόθεση.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Ρύθμιση δύο δακτύλων για την ενέργεια χτυπήματος</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Ρύθμιση τριών δακτύλων για την ενέργεια χτυπήματος</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Ρύθμιση ευαισθησίας της οθόνης αφής</string>
|
||||
<string name="mousepad_scroll_direction_title">Κατεύθυνση ανάστροφης κύλησης</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Δεξί κλικ</item>
|
||||
<item>Μεσαίο κλικ</item>
|
||||
<item>Τίποτα</item>
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">δεξί</string>
|
||||
<string name="mousepad_triple_default">μεσαίο</string>
|
||||
<string name="mousepad_sensitivity_default">προκαθορισμένο</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Το πιο αργό</item>
|
||||
<item>Πάνω από το πιο αργό</item>
|
||||
<item>Προκαθορισμένο</item>
|
||||
<item>Πάνω από το προκαθορισμένο</item>
|
||||
<item>Το ταχύτερο</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Συνδεδεμένες συσκευές</string>
|
||||
<string name="category_not_paired_devices">Διαθέσιμες συσκευές</string>
|
||||
<string name="category_remembered_devices">Συσκευές στη μνήμη</string>
|
||||
<string name="plugins_failed_to_load">Αποτυχία φόρτωσης προσθέτων (χτυπήστε για περισσότερες πληροφορίες):</string>
|
||||
<string name="device_menu_plugins">Ρυθμίσεις προσθέτων</string>
|
||||
<string name="device_menu_unpair">Διαχωρισμός</string>
|
||||
<string name="device_not_reachable">Η συζευγμένη συσκευή δεν είναι προσβάσιμη</string>
|
||||
<string name="pair_new_device">Σύζευξη νέας συσκευής</string>
|
||||
<string name="unknown_device">Άγνωστη συσκευή</string>
|
||||
<string name="error_not_reachable">Η συσκευή δεν είναι προσβάσιμη</string>
|
||||
<string name="error_already_requested">Η σύζευξη ήδη ζητήθηκε</string>
|
||||
<string name="error_already_paired">Η συσκευή ήδη συζεύχθηκε</string>
|
||||
<string name="error_could_not_send_package">Αδυναμία αποστολής πακέτου</string>
|
||||
<string name="error_timed_out">Τέλος χρονικού ορίου</string>
|
||||
<string name="error_canceled_by_user">Ακυρώθηκε από τον χρήστη</string>
|
||||
<string name="error_canceled_by_other_peer">Ακυρώθηκε από άλλον χρήστη</string>
|
||||
<string name="error_invalid_key">Ελήφθη μη έγκυρο κλειδί</string>
|
||||
<string name="encryption_info_title">Πληροφορίες κρυπτογράφησης</string>
|
||||
<string name="encryption_info_msg_no_ssl">Η άλλη συσκευή δεν χρησιμοποιεί μια πρόσφατη έκδοση του KDE Connect, θα χρησιμοποιηθεί η παλαιά μέθοδος κρυπτογράφησης.</string>
|
||||
<string name="my_device_fingerprint">Το ίχνος SHA1 του πιστοποιητικού της συσκευής σας είναι:</string>
|
||||
<string name="remote_device_fingerprint">Το ίχνος SHA1 του πιστοποιητικού της απομακρυσμένης συσκευής είναι:</string>
|
||||
<string name="pair_requested">Ζητήθηκε σύζευξη</string>
|
||||
<string name="pairing_request_from">Αίτημα σύζευξης από %1s</string>
|
||||
<string name="received_url_title">Ελήφθη σύνδεσμος από %1s</string>
|
||||
<string name="received_url_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="incoming_file_title">Εισερχόμενο αρχείο από %1s</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Αποστολή αρχείου σε %1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="received_file_title">Ελήφθη αρχείο από %1s</string>
|
||||
<string name="received_file_fail_title">Αποτυχία λήψης αρχείου από %1s</string>
|
||||
<string name="received_file_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="sent_file_title">Εστάλη αρχείο στο %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Αποτυχία αποστολής αρχείου %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Χτυπήστε για να απαντήσετε</string>
|
||||
<string name="reconnect">Επανασύνδεση</string>
|
||||
<string name="right_click">Αποστολή δεξιού κλικ</string>
|
||||
<string name="middle_click">Αποστολή μεσαίου κλικ</string>
|
||||
<string name="show_keyboard">Εμφάνιση πληκτρολογίου</string>
|
||||
<string name="device_not_paired">Η συσκευή δεν συζεύχθηκε</string>
|
||||
<string name="request_pairing">Αίτημα σύζευξης</string>
|
||||
<string name="pairing_accept">Αποδοχή</string>
|
||||
<string name="pairing_reject">Απόρριψη</string>
|
||||
<string name="device">Συσκευή</string>
|
||||
<string name="pair_device">Σύζευξη συσκευής</string>
|
||||
<string name="remote_control">Απομακρυσμένος έλεγχος</string>
|
||||
<string name="settings">Ρυθμίσεις KDE Connect</string>
|
||||
<string name="mpris_play">Αναπαραγωγή</string>
|
||||
<string name="mpris_previous">Προηγούμενο</string>
|
||||
<string name="mpris_rew">Ταχεία ώθηση όπισθεν</string>
|
||||
<string name="mpris_ff">Ταχεία προώθηση</string>
|
||||
<string name="mpris_next">Επόμενο</string>
|
||||
<string name="mpris_volume">Τόμος</string>
|
||||
<string name="mpris_settings">Ρυθμίσεις πολυμέσων</string>
|
||||
<string name="mpris_time_settings_title">Κουμπιά ταχείας ώθησης</string>
|
||||
<string name="mpris_time_settings_summary">Χρονική προσαρμογή ταχείας ώθησης ανάλογα με την πίεση.</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 seconds</item>
|
||||
<item>20 seconds</item>
|
||||
<item>30 seconds</item>
|
||||
<item>1 λεπτό</item>
|
||||
<item>2 λεπτά</item>
|
||||
</string-array>
|
||||
<string name="share_to">Διαμοιρασμός με...</string>
|
||||
<string name="protocol_version_older">Η συσκευή αυτή χρησιμοποιεί παλαιά έκδοση πρωτοκόλλου</string>
|
||||
<string name="protocol_version_newer">Η συσκευή αυτή χρησιμοποιεί νεότερη έκδοση πρωτοκόλλου</string>
|
||||
<string name="general_settings">Γενικές ρυθμίσεις</string>
|
||||
<string name="plugin_settings">Ρυθμίσεις</string>
|
||||
<string name="plugin_settings_with_name">%s ρυθμίσεις</string>
|
||||
<string name="device_name">Όνομα συσκευής</string>
|
||||
<string name="device_name_preference_summary">%s</string>
|
||||
<string name="invalid_device_name">Μη έγκυρο όνομα συσκευής</string>
|
||||
<string name="shareplugin_text_saved">Ελήφθη κείμενο, αποθηκεύτηκε στο πρόχειρο</string>
|
||||
<string name="custom_devices_settings">Προσαρμοσμένη λίστα συσκευών</string>
|
||||
<string name="pair_device_action">Σύζευξη νέας συσκευής</string>
|
||||
<string name="unpair_device_action">Διαχωρισμός %s</string>
|
||||
<string name="custom_device_list">Προσθήκη συσκευών ανά IP</string>
|
||||
<string name="share_notification_preference">Θορυβώδεις ειδοποιήσεις</string>
|
||||
<string name="share_notification_preference_summary">Δόνηση και ηχητική ένδειξη με τη λήψη αρχείου</string>
|
||||
<string name="title_activity_notification_filter">Φιλτράρισμα ειδοποιήσεων</string>
|
||||
<string name="filter_apps_info">Οι ειδοποιήσεις θα συγχρονίζονται για επιλεγμένες εφαρμογές.</string>
|
||||
<string name="sftp_internal_storage">Εσωτερικός αποθηκευτικός χώρος</string>
|
||||
<string name="sftp_all_files">Όλα τα αρχεία</string>
|
||||
<string name="sftp_sdcard_num">SD card %d</string>
|
||||
<string name="sftp_sdcard">SD card</string>
|
||||
<string name="sftp_readonly">(ανάγνωση μόνο)</string>
|
||||
<string name="sftp_camera">Φωτογραφίες</string>
|
||||
<string name="add_host">Προσθήκη υπολογιστή/IP</string>
|
||||
<string name="add_host_hint">Όνομα υπολογιστή ή IP</string>
|
||||
<string name="no_players_connected">Δεν βρέθηκαν συσκευές αναπαραγωγής</string>
|
||||
<string name="custom_dev_list_help">Χρησιμοποιήστε την επιλογή αυτή μόνο αν η συσκευή σας δεν έχει εντοπιστεί αυτόματα. Δώστε την IP διεύθυνση ή το όνομα του υπολογιστή και αγγίξτε το κουμπί για να προστεθεί στη λίστα. Αγγίξτε ένα αντικείμενο της λίστας για να το αφαιρέσετε.</string>
|
||||
<string name="mpris_player_on_device">%1$s σε %2$s</string>
|
||||
<string name="send_files">Αποστολή αρχείων</string>
|
||||
<string name="pairing_title">Συσκευές KDE Connect</string>
|
||||
<string name="pairing_description">Άλλες συσκευές με KDE Connect στο ίδιο δίκτυο θα εμφανίζονται εδώ.</string>
|
||||
<string name="device_paired">Η συσκευή συζεύχθηκε</string>
|
||||
<string name="device_rename_title">Μετονομασία συσκευής</string>
|
||||
<string name="device_rename_confirm">Μετονομασία</string>
|
||||
<string name="refresh">Ανανέωση</string>
|
||||
<string name="unreachable_description">Αυτή η συζευγμένη συσκευή δεν είναι προσβάσιμη. Βεβαιωθείτε ότι είναι συνδεδεμένη στο δίκτυό σας.</string>
|
||||
<string name="no_file_browser">Δεν υπάρχουν εγκατεστημένοι περιηγητές αρχείων.</string>
|
||||
<string name="pref_plugin_telepathy">Αποστολή SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Αποστολή μηνυμάτων κειμένου από τον υπολογιστή σας</string>
|
||||
<string name="plugin_not_supported">Αυτό το πρόσθετο δεν υποστηρίζεται από τη συσκευή</string>
|
||||
<string name="findmyphone_title">Αναζήτηση του κινητού μου</string>
|
||||
<string name="findmyphone_description">Καλεί αυτό το κινητό ώστε να το εντοπίσετε.</string>
|
||||
<string name="findmyphone_found">Βρέθηκε</string>
|
||||
<string name="open">Άνοιγμα</string>
|
||||
<string name="close">Κλείσιμο</string>
|
||||
</resources>
|
@@ -13,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>
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
|
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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() {
|
||||
|
@@ -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","");
|
||||
}
|
||||
|
||||
|
@@ -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) { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -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 {
|
||||
|
9
src/org/kde/kdeconnect/Helpers/StringsHelper.java
Normal file
9
src/org/kde/kdeconnect/Helpers/StringsHelper.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class StringsHelper {
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
}
|
@@ -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";
|
||||
|
@@ -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);
|
||||
|
@@ -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())) {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -170,7 +170,7 @@ public class ShareActivity extends ActionBarActivity {
|
||||
} catch (Exception e) {
|
||||
isUrl = false;
|
||||
}
|
||||
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE);
|
||||
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE_REQUEST);
|
||||
if (isUrl) {
|
||||
np.set("url", text);
|
||||
} else {
|
||||
|
@@ -58,7 +58,7 @@ import java.util.ArrayList;
|
||||
|
||||
public class SharePlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
//public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
||||
|
||||
final static boolean openUrlsDirectly = true;
|
||||
|
@@ -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);
|
||||
|
@@ -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 */
|
||||
|
@@ -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();
|
||||
|
@@ -20,27 +20,19 @@
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class LanLinkProviderTest extends AndroidTestCase {
|
||||
|
||||
private NioSocketAcceptor tcpAcceptor = null;
|
||||
private NioDatagramAcceptor udpAcceptor = null;
|
||||
private LanLinkProvider linkProvider;
|
||||
|
||||
@Override
|
||||
@@ -50,178 +42,35 @@ public class LanLinkProviderTest extends AndroidTestCase {
|
||||
System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath());
|
||||
|
||||
linkProvider = new LanLinkProvider(getContext());
|
||||
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("tcpAcceptor");
|
||||
field.setAccessible(true);
|
||||
tcpAcceptor = (NioSocketAcceptor)field.get(linkProvider);
|
||||
assertNotNull(tcpAcceptor);
|
||||
}catch (Exception e){
|
||||
fail("Error getting tcpAcceptor from LanLinkProvider");
|
||||
}
|
||||
|
||||
try{
|
||||
Field field = LanLinkProvider.class.getDeclaredField("udpAcceptor");
|
||||
field.setAccessible(true);
|
||||
udpAcceptor = (NioDatagramAcceptor)field.get(linkProvider);
|
||||
assertNotNull(udpAcceptor);
|
||||
}catch (Exception e){
|
||||
fail("Error getting udp acceptor from LanLinkProvider");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
tcpAcceptor.dispose();
|
||||
udpAcceptor.dispose();
|
||||
}
|
||||
|
||||
public void testTcpAcceptor(){
|
||||
|
||||
assertNotNull(tcpAcceptor.getHandler());
|
||||
assertEquals(tcpAcceptor.getSessionConfig().isKeepAlive(), true);
|
||||
assertEquals(tcpAcceptor.getSessionConfig().isReuseAddress(), true);
|
||||
assertNotNull(tcpAcceptor.getFilterChain().get("codec"));
|
||||
|
||||
}
|
||||
|
||||
public void testUdpAcceptor(){
|
||||
|
||||
assertNull(udpAcceptor.getHandler());
|
||||
assertEquals(udpAcceptor.getSessionConfig().isReuseAddress(), true);
|
||||
assertNotNull(udpAcceptor.getFilterChain().get("codec"));
|
||||
}
|
||||
|
||||
public void testOnStart() throws Exception{
|
||||
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
Mockito.when(session.getRemoteAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
|
||||
linkProvider.onStart();
|
||||
|
||||
assertNotNull(udpAcceptor.getHandler());
|
||||
assertEquals(udpAcceptor.getLocalAddress().getPort(), 1714);
|
||||
}
|
||||
|
||||
public void testUdpPackageReceived() throws Exception {
|
||||
|
||||
final int port = 5000;
|
||||
public void testIdentityPackageReceived() throws Exception{
|
||||
|
||||
NetworkPackage networkPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(networkPackage.getType()).thenReturn("kdeconnect.identity");
|
||||
Mockito.when(networkPackage.getString("deviceId")).thenReturn("testDevice");
|
||||
Mockito.when(networkPackage.getString("deviceName")).thenReturn("Test Device");
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(NetworkPackage.ProtocolVersion);
|
||||
Mockito.when(networkPackage.getString("deviceType")).thenReturn("phone");
|
||||
Mockito.when(networkPackage.getInt("tcpPort")).thenReturn(port);
|
||||
|
||||
final String serialized = "{\"type\":\"kdeconnect.identity\",\"id\":12345,\"body\":{\"deviceName\":\"Test Device\",\"deviceType\":\"phone\",\"deviceId\":\"testDevice\",\"protocolVersion\":5,\"tcpPort\": "+ port +"}}";
|
||||
Mockito.when(networkPackage.serialize()).thenReturn(serialized);
|
||||
|
||||
// Mocking udp session
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
Mockito.when(session.getRemoteAddress()).thenReturn(new InetSocketAddress(port));
|
||||
|
||||
|
||||
// Making a server socket, so that original LanLinkProvider can connect to it when it receives our fake package
|
||||
final ServerSocket serverSocket = new ServerSocket();
|
||||
serverSocket.bind(new InetSocketAddress(port));
|
||||
|
||||
final Thread thread = new Thread (new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
while (true) {
|
||||
if (inputStream.available() != 0) {
|
||||
// Data received from socket should be an identity package
|
||||
byte[] inputData = new byte[inputStream.available()];
|
||||
inputStream.read(inputData);
|
||||
|
||||
NetworkPackage receivedPackage = NetworkPackage.unserialize(new String(inputData));
|
||||
NetworkPackage identityPackage = NetworkPackage.createIdentityPackage(getContext());
|
||||
|
||||
// If any assertion fails, its output will be in logcat, not on test case thread anymore
|
||||
assertEquals(receivedPackage.getType(), identityPackage.getType());
|
||||
assertEquals(receivedPackage.getString("deviceName"), identityPackage.getString("deviceName"));
|
||||
assertEquals(receivedPackage.getString("deviceId"), identityPackage.getString("deviceId"));
|
||||
assertEquals(receivedPackage.getInt("protocolVersion"), identityPackage.getInt("protocolVersion"));
|
||||
|
||||
serverSocket.close();
|
||||
// Socket not closed to ensure visibleComputers contains entry for testDevice
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}catch (Exception e){
|
||||
assertEquals("Exception in thread",1,5);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
thread.start();
|
||||
linkProvider.onStart();
|
||||
udpAcceptor.getHandler().messageReceived(session, networkPackage.serialize());
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Wait 1 secs for our server, and then end test
|
||||
thread.join(1 * 1000);
|
||||
|
||||
// visibleComputers should contain an entry for testDevice
|
||||
HashMap<String, LanLink> visibleComputers;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("visibleComputers");
|
||||
field.setAccessible(true);
|
||||
visibleComputers = (HashMap<String, LanLink>)field.get(linkProvider);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNotNull(visibleComputers.get("testDevice"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testTcpIdentityPackageReceived() throws Exception{
|
||||
|
||||
IoSession session = Mockito.mock(IoSession.class);
|
||||
Mockito.when(session.getId()).thenReturn(12345l);
|
||||
|
||||
NetworkPackage networkPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(networkPackage.getType()).thenReturn("kdeconnect.identity");
|
||||
Mockito.when(networkPackage.getString("deviceId")).thenReturn("testDevice");
|
||||
Mockito.when(networkPackage.getString("deviceName")).thenReturn("Test Device");
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(NetworkPackage.ProtocolVersion);
|
||||
Mockito.when(networkPackage.getInt("protocolVersion")).thenReturn(5);
|
||||
Mockito.when(networkPackage.getString("deviceType")).thenReturn("phone");
|
||||
|
||||
String serialized = "{\"type\":\"kdeconnect.identity\",\"id\":12345,\"body\":{\"deviceName\":\"Test Device\",\"deviceType\":\"phone\",\"deviceId\":\"testDevice\",\"protocolVersion\":5}}";
|
||||
Mockito.when(networkPackage.serialize()).thenReturn(serialized);
|
||||
|
||||
Socket channel = Mockito.mock(Socket.class);
|
||||
try {
|
||||
tcpAcceptor.getHandler().messageReceived(session, networkPackage.serialize());
|
||||
Method method = LanLinkProvider.class.getDeclaredMethod("identityPackageReceived", NetworkPackage.class, Socket.class, LanLink.ConnectionStarted.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(linkProvider, networkPackage, channel, LanLink.ConnectionStarted.Locally);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
|
||||
LongSparseArray<LanLink> nioSessions;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("nioSessions");
|
||||
field.setAccessible(true);
|
||||
nioSessions = (LongSparseArray<LanLink>)field.get(linkProvider);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNotNull(nioSessions.get(12345l));
|
||||
|
||||
|
||||
HashMap<String, LanLink> visibleComputers;
|
||||
try {
|
||||
Field field = LanLinkProvider.class.getDeclaredField("visibleComputers");
|
||||
@@ -232,14 +81,5 @@ public class LanLinkProviderTest extends AndroidTestCase {
|
||||
}
|
||||
assertNotNull(visibleComputers.get("testDevice"));
|
||||
|
||||
|
||||
// Testing session closed
|
||||
try {
|
||||
tcpAcceptor.getHandler().sessionClosed(session);
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
assertNull(nioSessions.get(12345l));
|
||||
assertNull(visibleComputers.get("testDevice"));
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ package org.kde.kdeconnect;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.mockito.Mockito;
|
||||
@@ -35,19 +35,19 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
|
||||
public class LanLinkTest extends AndroidTestCase {
|
||||
|
||||
LanLink lanLink;
|
||||
Channel channel;
|
||||
Device.SendPackageStatusCallback callback;
|
||||
LanLink badLanLink;
|
||||
LanLink goodLanLink;
|
||||
|
||||
ChannelFuture channelFutureSuccess, channelFutureFailure;
|
||||
OutputStream badOutputStream;
|
||||
OutputStream goodOutputStream;
|
||||
|
||||
Device.SendPackageStatusCallback callback;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -58,35 +58,23 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
||||
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
||||
|
||||
channel = Mockito.mock(Channel.class);
|
||||
// Mockito.when(channel.hashCode()).thenReturn(12345);
|
||||
Mockito.when(channel.remoteAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
// Mockito.doReturn(new InetSocketAddress(5000)).when(channel).remoteAddress();
|
||||
|
||||
callback = Mockito.mock(Device.SendPackageStatusCallback.class);
|
||||
Mockito.doNothing().when(callback).sendSuccess();
|
||||
Mockito.doNothing().when(callback).sendProgress(Mockito.any(Integer.class));
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
throw (Throwable) invocationOnMock.getArguments()[0];
|
||||
}
|
||||
}).when(callback).sendFailure(Mockito.any(Throwable.class));
|
||||
|
||||
channelFutureSuccess = Mockito.mock(ChannelFuture.class);
|
||||
Mockito.when(channelFutureSuccess.isDone()).thenReturn(true);
|
||||
Mockito.when(channelFutureSuccess.isSuccess()).thenReturn(true);
|
||||
Mockito.when(channelFutureSuccess.channel()).thenReturn(channel);
|
||||
Mockito.when(channelFutureSuccess.sync()).thenReturn(channelFutureSuccess);
|
||||
goodOutputStream = Mockito.mock(OutputStream.class);
|
||||
badOutputStream = Mockito.mock(OutputStream.class);
|
||||
Mockito.doThrow(new IOException("AAA")).when(badOutputStream).write(Mockito.any(byte[].class));
|
||||
|
||||
channelFutureFailure = Mockito.mock(ChannelFuture.class);
|
||||
Mockito.when(channelFutureFailure.isDone()).thenReturn(true);
|
||||
Mockito.when(channelFutureFailure.isSuccess()).thenReturn(false);
|
||||
Mockito.when(channelFutureFailure.cause()).thenReturn(new IOException("Cannot send package"));
|
||||
Mockito.when(channelFutureFailure.channel()).thenReturn(channel);
|
||||
Mockito.when(channelFutureFailure.sync()).thenReturn(channelFutureFailure);
|
||||
|
||||
lanLink = new LanLink(getContext(), channel, "testDevice", linkProvider, BaseLink.ConnectionStarted.Remotely);
|
||||
Socket socketMock = Mockito.mock(Socket.class);
|
||||
Mockito.when(socketMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
Mockito.when(socketMock.getOutputStream()).thenReturn(goodOutputStream);
|
||||
|
||||
Socket socketBadMock = Mockito.mock(Socket.class);
|
||||
Mockito.when(socketBadMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000));
|
||||
Mockito.when(socketBadMock.getOutputStream()).thenReturn(badOutputStream);
|
||||
|
||||
goodLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketMock, LanLink.ConnectionStarted.Remotely);
|
||||
badLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketBadMock, LanLink.ConnectionStarted.Remotely);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,7 +82,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testSendPackageSuccess(){
|
||||
public void testSendPackageSuccess() throws JSONException {
|
||||
|
||||
NetworkPackage testPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(testPackage.getType()).thenReturn("kdeconnect.test");
|
||||
@@ -102,18 +90,12 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
Mockito.when(testPackage.getString("testName")).thenReturn("testSendPackageSuccess");
|
||||
Mockito.when(testPackage.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPackageSuccess\"}}");
|
||||
|
||||
try {
|
||||
Mockito.when(channel.writeAndFlush(testPackage.serialize())).thenReturn(channelFutureSuccess);
|
||||
lanLink.sendPackage(testPackage, callback);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
goodLanLink.sendPackage(testPackage, callback);
|
||||
|
||||
assertEquals(channelFutureSuccess.isDone(), true);
|
||||
assertEquals(channelFutureSuccess.isSuccess(), true);
|
||||
Mockito.verify(callback).sendSuccess();
|
||||
}
|
||||
|
||||
public void testSendPackageFail(){
|
||||
public void testSendPackageFail() throws JSONException {
|
||||
|
||||
NetworkPackage testPackage = Mockito.mock(NetworkPackage.class);
|
||||
Mockito.when(testPackage.getType()).thenReturn("kdeconnect.test");
|
||||
@@ -121,16 +103,10 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
Mockito.when(testPackage.getString("testName")).thenReturn("testSendPackageFail");
|
||||
Mockito.when(testPackage.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPackageFail\"}}");
|
||||
|
||||
try {
|
||||
Mockito.when(channel.writeAndFlush(testPackage.serialize())).thenReturn(channelFutureFailure);
|
||||
lanLink.sendPackage(testPackage, callback);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
badLanLink.sendPackage(testPackage, callback);
|
||||
|
||||
Mockito.verify(callback).sendFailure(Mockito.any(RuntimeException.class));
|
||||
|
||||
assertEquals(channelFutureFailure.isDone(), true);
|
||||
assertEquals(channelFutureFailure.isSuccess(), false);
|
||||
assertEquals(channelFutureFailure.cause() instanceof IOException, true );
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +133,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
try {
|
||||
socket = new Socket();
|
||||
int tcpPort = np.getPayloadTransferInfo().getInt("port");
|
||||
InetSocketAddress address = (InetSocketAddress)channel.remoteAddress();
|
||||
InetSocketAddress address = new InetSocketAddress(5000);
|
||||
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
np.setPayload(socket.getInputStream(), np.getPayloadSize());
|
||||
} catch (Exception e) {
|
||||
@@ -252,17 +228,18 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
|
||||
String stringNetworkPackage = (String)invocationOnMock.getArguments()[0];
|
||||
Log.e("LanLinkTest","Write to stream");
|
||||
String stringNetworkPackage = new String((byte[])invocationOnMock.getArguments()[0]);
|
||||
final NetworkPackage np = NetworkPackage.unserialize(stringNetworkPackage);
|
||||
|
||||
downloader.setNetworkPackage(np);
|
||||
downloader.start();
|
||||
|
||||
return channelFutureSuccess;
|
||||
return stringNetworkPackage.length();
|
||||
}
|
||||
}).when(channel).writeAndFlush(Mockito.anyString());
|
||||
}).when(goodOutputStream).write(Mockito.any(byte[].class));
|
||||
|
||||
lanLink.sendPackage(sharePackage, callback);
|
||||
goodLanLink.sendPackage(sharePackage, callback);
|
||||
|
||||
try {
|
||||
// Wait 1 secs for downloader to finish (if some error, it will continue and assert will fail)
|
||||
@@ -273,5 +250,7 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
}
|
||||
assertEquals(new String(data), new String(downloader.getOutputStream().toByteArray()));
|
||||
|
||||
Mockito.verify(callback).sendSuccess();
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -124,15 +125,13 @@ public class NetworkPackageTest extends AndroidTestCase{
|
||||
assertEquals(decrypted.getType(), copy.getType());
|
||||
assertEquals(decrypted.getJSONArray("body"), copy.getJSONArray("body"));
|
||||
|
||||
String json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":\"A really long package id\",\"payloadSize\":0,\"payloadTransferInfo\":{},\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n";
|
||||
String json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":945945945,\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n";
|
||||
NetworkPackage longJsonNp = NetworkPackage.unserialize(json);
|
||||
try {
|
||||
NetworkPackage encrypted = RsaHelper.encrypt(longJsonNp, publicKey);
|
||||
decrypted = RsaHelper.decrypt(encrypted, privateKey);
|
||||
|
||||
String decryptedJson = decrypted.serialize();
|
||||
assertEquals(json, decryptedJson);
|
||||
|
||||
JSONAssert.assertEquals(json, decryptedJson, true);
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
Reference in New Issue
Block a user