Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3804ede5fc | ||
|
92006f744f | ||
|
2a7107aecf | ||
|
a3cc399424 | ||
|
4dd8a5d86c |
@@ -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="910"
|
||||
android:versionName="0.9g">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9"
|
||||
android:targetSdkVersion="22" />
|
||||
@@ -14,16 +14,21 @@
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"
|
||||
android:required="false" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:required="false" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false" />
|
||||
<!-- <uses-permission android:name="android.permission.SEND_SMS" android:required="false" /> -->
|
||||
<uses-permission
|
||||
android:name="android.permission.RECEIVE_SMS"
|
||||
android:required="false" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
@@ -31,7 +36,6 @@
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:supportsRtl="true"
|
||||
android:label="KDE Connect"
|
||||
android:theme="@style/KdeConnectTheme"
|
||||
>
|
||||
@@ -115,12 +119,6 @@
|
||||
-->
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity"
|
||||
android:label="@string/findmyphone_title"
|
||||
android:launchMode="singleInstance">
|
||||
</activity>
|
||||
|
||||
<!-- Plugin-related activities and services -->
|
||||
|
||||
<activity
|
||||
@@ -132,15 +130,6 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MaterialActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandActivity"
|
||||
android:label="@string/remote_control"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MaterialActivity"
|
||||
>
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MaterialActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
|
@@ -22,4 +22,4 @@ You can install this app from the [Play Store](https://play.google.com/store/app
|
||||
## License
|
||||
[GNU GPL v2](https://www.gnu.org/licenses/gpl-2.0.html) and [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://projects.kde.org/projects/extragear/network/kdeconnect-android/repository/).
|
||||
If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://projects.kde.org/projects/playground/base/kdeconnect-android/repository/).
|
||||
|
41
build.gradle
@@ -3,28 +3,18 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||
classpath 'com.android.tools.build:gradle:1.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
buildToolsVersion '23.0.2'
|
||||
compileSdkVersion 23
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion '22.0.1'
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 22 //Bumping to 23 means we have to support the new permissions model
|
||||
multiDexEnabled true
|
||||
}
|
||||
dexOptions {
|
||||
javaMaxHeapSize "4g"
|
||||
}
|
||||
compileOptions {
|
||||
// Use Java 1.7, requires minSdk 8
|
||||
//SSHD requires mina when running on JDK < 7
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
targetSdkVersion 22
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -42,19 +32,14 @@ android {
|
||||
pickFirst "META-INF/DEPENDENCIES"
|
||||
pickFirst "META-INF/LICENSE"
|
||||
pickFirst "META-INF/NOTICE"
|
||||
pickFirst "META-INF/BCKEY.SF"
|
||||
pickFirst "META-INF/BCKEY.DSA"
|
||||
pickFirst "META-INF/INDEX.LIST"
|
||||
pickFirst "META-INF/io.netty.versions.properties"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
buildTypes {
|
||||
release { //keep on 'releae', set to 'all' when testing to make sure proguard is not deleting important stuff
|
||||
release {
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@@ -64,19 +49,19 @@ 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 'com.android.support:support-v4:22.2.1'
|
||||
compile 'com.android.support:appcompat-v7:22.2.1'
|
||||
compile 'com.android.support:design:22.2.1'
|
||||
|
||||
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 'org.apache.mina:mina-core:2.0.9'
|
||||
compile 'org.apache.sshd:sshd-core:0.8.0'
|
||||
compile 'org.bouncycastle:bcprov-jdk16:1.46'
|
||||
|
||||
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'
|
||||
|
||||
//compile fileTree(include: '*.jar', dir: 'libs')
|
||||
//compile fileTree(dir: 'libs', include: '*.jar')
|
||||
}
|
||||
|
15
proguard-rules.pro
vendored
@@ -16,24 +16,15 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
# Allow obfuscation of android.support.v7.internal.view.menu.**
|
||||
# to avoid problem on Samsung 4.2.2 devices with appcompat v21
|
||||
# see https://code.google.com/p/android/issues/detail?id=78377
|
||||
-keepnames class !android.support.v7.internal.view.menu.**,android.support.v7.** {*;}
|
||||
-keep class !android.support.v7.internal.view.menu.**,** {*;}
|
||||
|
||||
-dontwarn org.spongycastle.**
|
||||
-dontwarn org.apache.sshd.**
|
||||
-dontwarn org.apache.mina.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.slf4j.**
|
||||
-dontwarn io.netty.**
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable,Signature,*Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
-keep class org.spongycastle.** {*;}
|
||||
|
||||
# SSHd requires mina, and mina uses reflection so some classes would get deleted
|
||||
-keep class org.apache.mina.** {*;}
|
||||
|
||||
-keep class org.kde.kdeconnect.** {*;}
|
||||
|
Before Width: | Height: | Size: 959 B |
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 553 B |
Before Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 667 B |
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
@@ -26,6 +26,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/device_not_paired"
|
||||
android:id="@+id/pair_message"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
/>
|
||||
|
||||
<Button
|
||||
@@ -73,14 +74,10 @@
|
||||
android:id="@+id/unpair_message"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:drawableStart="@drawable/ic_error_outline_black_48dp"
|
||||
android:drawableLeft="@drawable/ic_error_outline_black_48dp"
|
||||
android:drawablePadding="8dip"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/unreachable_description"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
android:gravity="center" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/buttons_list"
|
||||
|
@@ -1,26 +0,0 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity">
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:text="@string/findmyphone_found"
|
||||
android:textSize="50dp"
|
||||
android:id="@+id/bFindMyPhone"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
|
||||
|
||||
|
||||
|
||||
</RelativeLayout>
|
@@ -12,27 +12,22 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
android:elevation="8dp"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.design.widget.NavigationView
|
||||
android:id="@+id/navigation_drawer"
|
||||
android:background="@drawable/state_list_drawer_background"
|
||||
app:itemBackground="@drawable/state_list_drawer_background"
|
||||
app:itemTextColor="@color/state_list_drawer_text"
|
||||
app:itemIconTint="@color/state_list_drawer_text"
|
||||
|
@@ -29,8 +29,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:singleLine="true"
|
||||
android:textColor="#CC2222"
|
||||
android:visibility="gone"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:text="" />
|
||||
|
||||
|
||||
|
@@ -7,9 +7,9 @@
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/mpris_control_view"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="30dip"
|
||||
android:paddingLeft="60dip"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingRight="30dip"
|
||||
android:paddingRight="60dip"
|
||||
android:paddingBottom="5dip">
|
||||
|
||||
<TextView
|
||||
@@ -32,9 +32,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/now_playing_textview"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:padding="8dip"
|
||||
@@ -151,6 +148,7 @@
|
||||
android:layout_marginEnd="10dip"
|
||||
android:id="@+id/imageView"
|
||||
android:layout_weight="0"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:contentDescription="@string/mpris_volume"
|
||||
android:src="@drawable/ic_volume"
|
||||
/>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"
|
||||
android:background="@drawable/drawer_header"
|
||||
@@ -55,4 +55,4 @@
|
||||
/>
|
||||
-->
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
@@ -18,13 +18,14 @@
|
||||
Preference is able to place a specific widget for its particular
|
||||
type in the "widget_frame" layout. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:res="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingEnd="?android:attr/scrollbarSize"
|
||||
android:paddingRight="?android:attr/scrollbarSize"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
android:background="?attr/selectableItemBackground" >
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@+android:id/widget_frame"
|
||||
|
@@ -27,13 +27,6 @@
|
||||
<item/>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">الأجهزة المقترن بها</string>
|
||||
<string name="category_not_paired_devices">الأجهزة المتوفّرة</string>
|
||||
<string name="category_remembered_devices">الأجهزة المتذكَّرة</string>
|
||||
|
@@ -4,20 +4,19 @@
|
||||
<string name="pref_plugin_telephony_desc">Unvia avisos pa SMS y llamaes</string>
|
||||
<string name="pref_plugin_battery">Informe de batería</string>
|
||||
<string name="pref_plugin_battery_desc">Infoma davezu del estáu de la batería</string>
|
||||
<string name="pref_plugin_sftp">Esposición del sistema de ficheros</string>
|
||||
<string name="pref_plugin_sftp_desc">Permite restolar remotamente\'l sistema de ficheros del teléfonu</string>
|
||||
<string name="pref_plugin_clipboard">Sincronización del cartafueyu</string>
|
||||
<string name="pref_plugin_clipboard_desc">Comparte\'l conteníu del cartafueyu</string>
|
||||
<string name="pref_plugin_mousepad">Entrada remota</string>
|
||||
<string name="pref_plugin_mousepad_desc">Usa\'l to teléfonu como mur y tecláu</string>
|
||||
<string name="pref_plugin_mpris">Controles remotos multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla l\'audiu/videu dende\'l to teléfonu</string>
|
||||
<string name="pref_plugin_runcommand">Executar comandu</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla audiu/videu dende\'l to teléfonu</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa un comandu nel to sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Unvia y recibe pings</string>
|
||||
<string name="pref_plugin_ping_desc">Unvia y recibi pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronización d\'avisos</string>
|
||||
<string name="pref_plugin_notifications_desc">Accede a los tos avisos dende otros preseos</string>
|
||||
<string name="pref_plugin_sharereceiver">Compartir y recibir</string>
|
||||
<string name="pref_plugin_sharereceiver">Unvia y recibi pings</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Comparte ficheros y URLs ente preseos</string>
|
||||
<string name="plugin_not_available">Esta carauterística nun ta disponible na to versión d\'Android</string>
|
||||
<string name="device_list_empty">Ensin preseos</string>
|
||||
@@ -28,34 +27,16 @@
|
||||
<string name="send_ping">Unviar ping</string>
|
||||
<string name="open_mpris_controls">Controles multimedia</string>
|
||||
<string name="open_mousepad">Entrada remota</string>
|
||||
<string name="mousepad_info">Muevi un deu enriba la pantalla pa mover el mur. Calca pa un clic y usa dos/tres deos pa los botones de drecha y en mediu. Usa un primíu llargu p\'arrastrar y soltar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Afitar aición de calcu con dos deos</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Afitar aición de calcu con tres deos</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Afitar sensibilidá del panel táutil</string>
|
||||
<string name="mousepad_scroll_direction_title">Direición inversa de desplazamientu</string>
|
||||
<string name="mousepad_info">Muevi un deu enriba la pantalla pa mover el mur. calca pa un clic y usa dos/tres deos pa los motones de drecha y mediu. Usa un primíu llargu pa arrastrar y soltar.</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Clic drechu</item>
|
||||
<item>Clic d\'en mediu</item>
|
||||
<item>Nada</item>
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">drecha</string>
|
||||
<string name="mousepad_triple_default">d\'en mediu</string>
|
||||
<string name="mousepad_sensitivity_default">por defeutu</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>La más lenta</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Predeterminada</item>
|
||||
<item>Penriba lo predeterminao</item>
|
||||
<item>La más rápida</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Preseos coneutaos</string>
|
||||
<string name="category_not_paired_devices">Preseos disponibles</string>
|
||||
<string name="category_remembered_devices">Preseos recordaos</string>
|
||||
<string name="plugins_failed_to_load">Los complementos fallaron al cargase (calca pa más información):</string>
|
||||
<string name="device_menu_plugins">Axustes de complementos</string>
|
||||
<string name="device_menu_unpair">Desempareyar</string>
|
||||
<string name="device_not_reachable">El preséu empareyáu nun ye agamable</string>
|
||||
<string name="pair_new_device">Empareyar preséu nuevu</string>
|
||||
<string name="unknown_device">Preséu desconocíu</string>
|
||||
<string name="error_not_reachable">Nun ye algamable\'l preséu</string>
|
||||
<string name="error_already_requested">Empareyamientu yá solicitáu</string>
|
||||
@@ -82,8 +63,6 @@
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Calca pa responder</string>
|
||||
<string name="reconnect">Reconeutar</string>
|
||||
<string name="right_click">Unviar clic drechu</string>
|
||||
<string name="middle_click">Unviar clic d\'en mediu</string>
|
||||
<string name="show_keyboard">Amosar tecláu</string>
|
||||
<string name="device_not_paired">Preséu non empareyáu</string>
|
||||
<string name="request_pairing">Solicitar empareyamientu</string>
|
||||
@@ -109,7 +88,6 @@
|
||||
<item>1 minutu</item>
|
||||
<item>2 minutos</item>
|
||||
</string-array>
|
||||
<string name="share_to">Compartir en...</string>
|
||||
<string name="protocol_version_older">Esti preséu usa una versión vieya del protocolu</string>
|
||||
<string name="protocol_version_newer">Esti preséu usa una versión anovada del protocolu</string>
|
||||
<string name="general_settings">Axustes xenerales</string>
|
||||
@@ -140,7 +118,7 @@
|
||||
<string name="mpris_player_on_device">%1$s en %2$s</string>
|
||||
<string name="send_files">Unviar ficheros</string>
|
||||
<string name="pairing_title">Preseos KDE Connect</string>
|
||||
<string name="pairing_description">Equí deberíen apaecer otros preseos executando KDE Connect.</string>
|
||||
<string name="pairing_description">Deberíen apaecer equí otros preseos executando KDE Connect.</string>
|
||||
<string name="device_paired">Preséu empareyáu</string>
|
||||
<string name="device_rename_title">Renomar preséu</string>
|
||||
<string name="device_rename_confirm">Renomar</string>
|
||||
@@ -150,8 +128,7 @@
|
||||
<string name="pref_plugin_telepathy">Unviar SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Unvia mensaxes de testu dende\'l to escritoriu</string>
|
||||
<string name="plugin_not_supported">Esti complementu nun ta sofitáu pol preséu</string>
|
||||
<string name="findmyphone_title">Alcuéntra\'l mio teléfonu</string>
|
||||
<string name="findmyphone_description">Fai sonar esti teléfonu pa que pueas alcontralu.</string>
|
||||
<string name="findmyphone_found">Alcontrar</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="close">Zarrar</string>
|
||||
</resources>
|
||||
|
@@ -25,13 +25,6 @@
|
||||
<item>Middle click</item>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Свързани устройства</string>
|
||||
<string name="category_remembered_devices">Запомнени устройства</string>
|
||||
<string name="plugins_failed_to_load">Неуспешно зареждане на приставки (докоснете за подробности)</string>
|
||||
|
@@ -32,13 +32,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">desno</string>
|
||||
<string name="mousepad_triple_default">Srednje</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Spojeni uređaji</string>
|
||||
<string name="category_not_paired_devices">Dostupni uređaji</string>
|
||||
<string name="category_remembered_devices">Zapamćeni uređaji</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controls multimèdia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla l\'àudio i el vídeo del vostre telèfon</string>
|
||||
<string name="pref_plugin_runcommand">Executa una ordre</string>
|
||||
<string name="pref_plugin_runcommand_desc">Activa les ordres remotes des del vostre telèfon</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa una ordre al vostre sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envia i rep els pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronitza les notificacions</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Moveu un dit per la pantalla per a moure el cursor del ratolí. Toqueu per un clic, i empreu dos/tres dits pels botons dret i mig. Empreu un toc llarg per arrossegar i deixar anar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Estableix l\'acció de tocar amb dos dits</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Estableix l\'acció de tocar amb tres dits</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Estableix la sensibilitat del ratolí tàctil</string>
|
||||
<string name="mousepad_scroll_direction_title">Inverteix la direcció del desplaçament</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Clic dret</item>
|
||||
<item>Clic del mig</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">dret</string>
|
||||
<string name="mousepad_triple_default">mig</string>
|
||||
<string name="mousepad_sensitivity_default">Predeterminada</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>La més lenta</item>
|
||||
<item>Lenta</item>
|
||||
<item>Predeterminada</item>
|
||||
<item>Ràpida</item>
|
||||
<item>La més ràpida</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositius connectats</string>
|
||||
<string name="category_not_paired_devices">Dispositius disponibles</string>
|
||||
<string name="category_remembered_devices">Dispositius recordats</string>
|
||||
@@ -66,10 +56,6 @@
|
||||
<string name="error_canceled_by_user">Cancel·lat per l\'usuari</string>
|
||||
<string name="error_canceled_by_other_peer">Cancel·lat per l\'altre parell</string>
|
||||
<string name="error_invalid_key">S\'ha rebut una clau no vàlida</string>
|
||||
<string name="encryption_info_title">Informació d\'encriptatge</string>
|
||||
<string name="encryption_info_msg_no_ssl">L\'altre dispositiu no usa una versió recent del KDE Connect, s\'utilitzarà el mètode d\'encriptatge antic.</string>
|
||||
<string name="my_device_fingerprint">L\'empremta digital SHA1 del certificat del vostre dispositiu és:</string>
|
||||
<string name="remote_device_fingerprint">L\'empremta digital SHA1 del certificat del dispositiu remot és:</string>
|
||||
<string name="pair_requested">S\'ha demanat aparellar</string>
|
||||
<string name="pairing_request_from">S\'ha demanat aparellar des de %1s</string>
|
||||
<string name="received_url_title">S\'ha rebut un vincle des de %1s</string>
|
||||
@@ -155,9 +141,7 @@
|
||||
<string name="pref_plugin_telepathy">Envia un SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Envia missatges de text des de l\'escriptori</string>
|
||||
<string name="plugin_not_supported">Aquest connector no és admès pel dispositiu</string>
|
||||
<string name="findmyphone_title">Cerca el meu telèfon</string>
|
||||
<string name="findmyphone_title">Troba el meu telèfon</string>
|
||||
<string name="findmyphone_description">Fa sonar aquest telèfon perquè el pugueu trobar.</string>
|
||||
<string name="findmyphone_found">L\'he trobat</string>
|
||||
<string name="open">Obre</string>
|
||||
<string name="close">Tanca</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Ovládání multimédií</string>
|
||||
<string name="pref_plugin_mpris_desc">Ovládejte audio/video z vašeho telefonu</string>
|
||||
<string name="pref_plugin_runcommand">Spustit příkaz</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustí příkaz na vašem počítači</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Posílat a přijímat ping</string>
|
||||
<string name="pref_plugin_notifications">Synchronizace upozornění</string>
|
||||
@@ -31,8 +32,6 @@
|
||||
<string name="mousepad_info">Pohybujte prstem po obrazovce pro pohybování kurzorem myši. Ťukněte pro kliknutí a použijte dva/tři prsty jako pravé a prostřední tlačítko. Pro přetažení dlouze podržte.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Nastavit činnost pro ťuknutí dvěma prsty</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Nastavit činnost pro ťuknutí třemi prsty</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Nastavit citlivost touchpadu</string>
|
||||
<string name="mousepad_scroll_direction_title">Obrácený směr posunu</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Kliknutí pravým tlačítkem myši</item>
|
||||
<item>Kliknutí prostředním tlačítkem myši</item>
|
||||
@@ -40,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">pravé</string>
|
||||
<string name="mousepad_triple_default">prostřední</string>
|
||||
<string name="mousepad_sensitivity_default">výchozí</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Nejpomalejší</item>
|
||||
<item>Méně pomalý</item>
|
||||
<item>Výchozí</item>
|
||||
<item>Rychlejší</item>
|
||||
<item>Nejrychlejší</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Připojená zařízení</string>
|
||||
<string name="category_not_paired_devices">Dostupná zařízení</string>
|
||||
<string name="category_remembered_devices">Zapamatovaná zařízení</string>
|
||||
@@ -55,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Nastavení modulů</string>
|
||||
<string name="device_menu_unpair">Zrušit párování</string>
|
||||
<string name="device_not_reachable">Spárované zařízení je nedostupné</string>
|
||||
<string name="pair_new_device">Spárovat nové zařízení</string>
|
||||
<string name="unknown_device">Neznámé zařízení</string>
|
||||
<string name="error_not_reachable">Zařízení je nedostupné</string>
|
||||
<string name="error_already_requested">Párování již bylo vyžádáno</string>
|
||||
@@ -150,8 +140,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>
|
||||
<string name="close">Zavřít</string>
|
||||
</resources>
|
||||
|
@@ -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ører en kommando på dit system</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>
|
||||
@@ -38,13 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">højre</string>
|
||||
<string name="mousepad_triple_default">midter</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Forbundne enheder</string>
|
||||
<string name="category_not_paired_devices">Tilgængelig enheder</string>
|
||||
<string name="category_remembered_devices">Huskede enheder</string>
|
||||
@@ -52,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Plugin-indstillinger</string>
|
||||
<string name="device_menu_unpair">Fjern parring</string>
|
||||
<string name="device_not_reachable">Den parrede enhed kan ikke tilgås</string>
|
||||
<string name="pair_new_device">Par med ny enhed</string>
|
||||
<string name="unknown_device">Ukendt enhed</string>
|
||||
<string name="error_not_reachable">Enheden kan ikke nås</string>
|
||||
<string name="error_already_requested">Allerede anmodet om parring</string>
|
||||
@@ -147,8 +140,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>
|
||||
<string name="close">Luk</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimedia-Bedienung</string>
|
||||
<string name="pref_plugin_mpris_desc">Audio und Video mit Ihrem Telefon steuern</string>
|
||||
<string name="pref_plugin_runcommand">Befehl ausführen</string>
|
||||
<string name="pref_plugin_runcommand_desc">Von Ihrem Telefon Befehle auf anderen Geräten ausführen</string>
|
||||
<string name="pref_plugin_runcommand_desc">Führt einen Befehl auf Ihrem System aus</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Senden und Empfangen von Pings</string>
|
||||
<string name="pref_plugin_notifications">Benachrichtigungs-Abgleich</string>
|
||||
@@ -29,10 +29,6 @@
|
||||
<string name="send_ping">Ping senden</string>
|
||||
<string name="open_mpris_controls">Multimedia-Bedienung</string>
|
||||
<string name="open_mousepad">Ferneingabe</string>
|
||||
<string name="mousepad_double_tap_settings_title">Aktionsausführung bei Berührung mit zwei Fingern einstellen</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Aktionsausführung bei Berührung mit drei Fingern einstellen</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Empfindlichkeit des Touchpads einstellen</string>
|
||||
<string name="mousepad_scroll_direction_title">Bildlaufrichtung umkehren</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Rechtsklick</item>
|
||||
<item>Mittelklick</item>
|
||||
@@ -40,14 +36,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">Rechts</string>
|
||||
<string name="mousepad_triple_default">Mitte</string>
|
||||
<string name="mousepad_sensitivity_default">Standard</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Langsamste</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Standard</item>
|
||||
<item>Above Default</item>
|
||||
<item>Schnellste</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Verbundene Geräte</string>
|
||||
<string name="category_not_paired_devices">Verfügbare Gerät</string>
|
||||
<string name="category_remembered_devices">Gemerkte Geräte</string>
|
||||
@@ -65,9 +53,6 @@
|
||||
<string name="error_canceled_by_user">Abbruch durch Benutzer</string>
|
||||
<string name="error_canceled_by_other_peer">Abbruch durch Gegenstelle</string>
|
||||
<string name="error_invalid_key">Ungültiger Schlüssel empfangen</string>
|
||||
<string name="encryption_info_title">Verschlüsselungsinformationen</string>
|
||||
<string name="my_device_fingerprint">Der SHA1-Fingerabdruck Ihres Gerätezertifikats lautet:</string>
|
||||
<string name="remote_device_fingerprint">Der SHA1-Fingerabdruck des Gerätezertifikats der Gegenstelle lautet:</string>
|
||||
<string name="pair_requested">Verbindung angefordert</string>
|
||||
<string name="pairing_request_from">Verbindungsanfrage von %1s</string>
|
||||
<string name="received_url_title">Verknüpfung von %1s erhalten</string>
|
||||
@@ -101,7 +86,6 @@
|
||||
<string name="mpris_next">Weiter</string>
|
||||
<string name="mpris_volume">Lautstärke</string>
|
||||
<string name="mpris_settings">Multimedia-Einstellungen</string>
|
||||
<string name="mpris_time_settings_title">Knöpfe Vorwärts/Rückwärts</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 Sekunden</item>
|
||||
<item>20 Sekunden</item>
|
||||
@@ -123,7 +107,6 @@
|
||||
<string name="pair_device_action">Ein neues Gerät verbinden</string>
|
||||
<string name="unpair_device_action">Verbindung %s trennen</string>
|
||||
<string name="custom_device_list">Geräte nach IP hinzufügen</string>
|
||||
<string name="share_notification_preference">Ausführliche Benachrichtigungen</string>
|
||||
<string name="share_notification_preference_summary">Beim Empfang einer Datei vibrieren und einen Sound abspielen</string>
|
||||
<string name="title_activity_notification_filter">Benachrichtigungs-Filter</string>
|
||||
<string name="filter_apps_info">Benachrichtigungen werden zwischen den ausgewählten Anwendungen abgeglichen.</string>
|
||||
@@ -139,18 +122,11 @@
|
||||
<string name="mpris_player_on_device">%1$s auf %2$s</string>
|
||||
<string name="send_files">Dateien senden</string>
|
||||
<string name="pairing_title">KDE-Connect-Geräte</string>
|
||||
<string name="pairing_description">Andere Geräte, auf denen KDE-Connect läuft im gleichen Netzwerk,sollte hier angezeigt werden.</string>
|
||||
<string name="device_paired">Gerät verbunden</string>
|
||||
<string name="device_rename_title">Geräte umbenennen</string>
|
||||
<string name="device_rename_confirm">Umbenennen</string>
|
||||
<string name="refresh">Aktualisieren</string>
|
||||
<string name="no_file_browser">Es sind keine Dateiverwaltungsprogramme installiert.</string>
|
||||
<string name="pref_plugin_telepathy">SMS senden</string>
|
||||
<string name="pref_plugin_telepathy_desc">Text-Nachrichten von Ihrer Arbeitsfläche senden</string>
|
||||
<string name="plugin_not_supported">Dieses Modul wird durch das Gerät nicht unterstützt</string>
|
||||
<string name="findmyphone_title">Mein Telefon suchen</string>
|
||||
<string name="findmyphone_description">Ruft dieses Telefon an, um es zu suchen.</string>
|
||||
<string name="findmyphone_found">Gefunden</string>
|
||||
<string name="open">Öffnen</string>
|
||||
<string name="close">Schließen</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimedia controls</string>
|
||||
<string name="pref_plugin_mpris_desc">Control audio/video from your phone</string>
|
||||
<string name="pref_plugin_runcommand">Run Command</string>
|
||||
<string name="pref_plugin_runcommand_desc">Runs a command on your system</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send and receive pings</string>
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
@@ -31,8 +32,6 @@
|
||||
<string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use a long press to drag\'n drop.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Set two finger tap action</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Set three finger tap action</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Set touchpad sensitivity</string>
|
||||
<string name="mousepad_scroll_direction_title">Reverse Scrolling Direction</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Right click</item>
|
||||
<item>Middle click</item>
|
||||
@@ -40,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">right</string>
|
||||
<string name="mousepad_triple_default">middle</string>
|
||||
<string name="mousepad_sensitivity_default">default</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Connected devices</string>
|
||||
<string name="category_not_paired_devices">Available devices</string>
|
||||
<string name="category_remembered_devices">Remembered devices</string>
|
||||
@@ -55,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Plugin settings</string>
|
||||
<string name="device_menu_unpair">Unpair</string>
|
||||
<string name="device_not_reachable">Paired device not reachable</string>
|
||||
<string name="pair_new_device">Pair new device</string>
|
||||
<string name="unknown_device">Unknown device</string>
|
||||
<string name="error_not_reachable">Device not reachable</string>
|
||||
<string name="error_already_requested">Pairing already requested</string>
|
||||
@@ -150,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Send SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string>
|
||||
<string name="plugin_not_supported">This plugin is not supported by the device</string>
|
||||
<string name="findmyphone_title">Find My Phone</string>
|
||||
<string name="findmyphone_description">Rings this phone so you can find it.</string>
|
||||
<string name="findmyphone_found">Found</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="close">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">Ejecuta una orden en su sistema</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>
|
||||
@@ -31,8 +32,6 @@
|
||||
<string name="mousepad_info">Mueva un dedo sobre la pantalla para mover el cursor del ratón. Pulse para ejecutar un clic y use dos/tres dedos para emular los botones derecho y central. Use una pulsación larga para arrastrar y soltar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Establecer la acción al pulsar con dos dedos</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Establecer la acción al pulsar con tres dedos</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Establecer sensibilidad del panel táctil</string>
|
||||
<string name="mousepad_scroll_direction_title">Invertir dirección de desplazamiento</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Clic derecho</item>
|
||||
<item>Clic del botón central</item>
|
||||
@@ -40,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">derecho</string>
|
||||
<string name="mousepad_triple_default">medio</string>
|
||||
<string name="mousepad_sensitivity_default">por defecto</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Muy poco sensible</item>
|
||||
<item>Poco sensible</item>
|
||||
<item>Predeterminada</item>
|
||||
<item>Sensible</item>
|
||||
<item>Muy sensible</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivos conectados</string>
|
||||
<string name="category_not_paired_devices">Dispositivos disponibles</string>
|
||||
<string name="category_remembered_devices">Dispositivos recordados</string>
|
||||
@@ -55,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Preferencias del complemento</string>
|
||||
<string name="device_menu_unpair">Desvincular</string>
|
||||
<string name="device_not_reachable">No se encuentra el dispositivo aparejado</string>
|
||||
<string name="pair_new_device">Vincular nuevo dispositivo</string>
|
||||
<string name="unknown_device">Dispositivo desconocido</string>
|
||||
<string name="error_not_reachable">No se encuentra el dispositivo</string>
|
||||
<string name="error_already_requested">Ya ha solicitado vincularse</string>
|
||||
@@ -150,8 +140,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>
|
||||
<string name="close">Cerrar</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimedian ohjaus</string>
|
||||
<string name="pref_plugin_mpris_desc">Ohjaa ääntä ja videota puhelimestasi</string>
|
||||
<string name="pref_plugin_runcommand">Suorita komento</string>
|
||||
<string name="pref_plugin_runcommand_desc">Suorita komento järjestelmässäsi</string>
|
||||
<string name="pref_plugin_ping">Tiedustelupaketti</string>
|
||||
<string name="pref_plugin_ping_desc">Lähetä ja vastaanota tiedustelupaketteja</string>
|
||||
<string name="pref_plugin_notifications">Ilmoitusten synkronointi</string>
|
||||
@@ -31,8 +32,6 @@
|
||||
<string name="mousepad_info">Liikuta hiiren osoitinta liikuttamalla sormeasi näytöllä. Napsauta napauttamalla yhdellä sormella, käytä oikeaa painiketta kahdella sormella ja keskipainiketta kolmella. Vedä ja pudota painamalla pitkään.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Aseta kahden sormen napautuksen toiminto</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Aseta kolmen sormen napautuksen toiminto</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Aseta kosketuslevyn herkkyys</string>
|
||||
<string name="mousepad_scroll_direction_title">Käänteinen vierityssuunta</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Oikea napsautus</item>
|
||||
<item>Keskinapsautus</item>
|
||||
@@ -40,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">Oikea painike</string>
|
||||
<string name="mousepad_triple_default">Keskipainike</string>
|
||||
<string name="mousepad_sensitivity_default">oletus</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Hitain</item>
|
||||
<item>Hitainta suurempi</item>
|
||||
<item>Oletus</item>
|
||||
<item>Oletusta suurempi</item>
|
||||
<item>Nopein</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Yhdistetyt laitteet</string>
|
||||
<string name="category_not_paired_devices">Saatavilla olevat laitteet</string>
|
||||
<string name="category_remembered_devices">Muistetut laitteet</string>
|
||||
@@ -55,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Liitännäisten asetukset</string>
|
||||
<string name="device_menu_unpair">Poista laitepari</string>
|
||||
<string name="device_not_reachable">Laitepari tavoittamattomissa</string>
|
||||
<string name="pair_new_device">Kytke uusi laite pariksi</string>
|
||||
<string name="unknown_device">Tuntematon laite</string>
|
||||
<string name="error_not_reachable">Laite tavoittamattomissa</string>
|
||||
<string name="error_already_requested">Pariksi kytkemistä on jo pyydetty</string>
|
||||
@@ -136,13 +126,13 @@
|
||||
<string name="add_host">Lisää kone/IP</string>
|
||||
<string name="add_host_hint">Konenimi tai IP-osoite</string>
|
||||
<string name="no_players_connected">Soittimia ei löytynyt</string>
|
||||
<string name="custom_dev_list_help">Käytä tätä vain, jos laitettasi ei tunnisteta automaattisesti. Kirjoita IP-osoite tai konenimi alle ja lisää se luetteloon koskettamalla painiketta. Voit poistaa olemassa olevan kohdan luettelosta koskettamalla sitä.</string>
|
||||
<string name="custom_dev_list_help">Käytä tätä vain, jos laitettasi ei tunnisteta automaattisesti. Kirjoita IP-osoite tai konenimi alle ja kosketa painiketta lisätäksesi sen luetteloon. Kosketa olemassa olevaa kohtaa poistaaksesi sen luettelosta.</string>
|
||||
<string name="mpris_player_on_device">%1$s laitteella %2$s</string>
|
||||
<string name="send_files">Lähetä tiedostoja</string>
|
||||
<string name="pairing_title">KDE Connect -laitteet</string>
|
||||
<string name="pairing_description">Muiden samassa verkossa olevien KDE Connectia käyttävien laitteiden pitäisi ilmestyä tähän.</string>
|
||||
<string name="device_paired">Laite kytketty pariksi</string>
|
||||
<string name="device_rename_title">Muuta laitteen nimeä</string>
|
||||
<string name="device_rename_title">Laitteen nimen muuttaminen</string>
|
||||
<string name="device_rename_confirm">Muuta nimi</string>
|
||||
<string name="refresh">Päivitä</string>
|
||||
<string name="unreachable_description">Tämä pariksi kytketty laite ei ole tavoitettavissa. Tarkista, että se on yhteydessä samaan verkkoon.</string>
|
||||
@@ -150,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Lähetä tekstiviesti</string>
|
||||
<string name="pref_plugin_telepathy_desc">Lähetä tekstiviestejä työpöydältäsi</string>
|
||||
<string name="plugin_not_supported">Laite ei tue tätä liitännäistä</string>
|
||||
<string name="findmyphone_title">Löydä puhelimeni</string>
|
||||
<string name="findmyphone_description">Laittaa puhelimen soimaan, jotta voit löytää sen.</string>
|
||||
<string name="findmyphone_found">Löytyi</string>
|
||||
<string name="open">Avaa</string>
|
||||
<string name="close">Sulje</string>
|
||||
</resources>
|
||||
|
@@ -37,13 +37,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">Droite</string>
|
||||
<string name="mousepad_triple_default">Milieu</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Périphériques connectés</string>
|
||||
<string name="category_not_paired_devices">Périphériques disponibles</string>
|
||||
<string name="category_remembered_devices">Périphériques mémorisés</string>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controis multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controle o son e o vídeo desde o teléfono.</string>
|
||||
<string name="pref_plugin_runcommand">Executar unha orde</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa unha orde no sistema.</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envíe e reciba pings.</string>
|
||||
<string name="pref_plugin_notifications">Sincronización de notificacións</string>
|
||||
@@ -31,8 +32,6 @@
|
||||
<string name="mousepad_info">Mova un dedo na pantalla para mover o cursor. Toque para facer clic, e use dous ou tres dedos para os botóns secundario e central. Prema durante un tempo para arrastrar e soltar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Definir a acción de tocar con dous dedos</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Definir a acción de tocar con tres dedos</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do punteiro táctil</string>
|
||||
<string name="mousepad_scroll_direction_title">Inverter a dirección de desprazamento</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Clic dereito</item>
|
||||
<item>Clic central</item>
|
||||
@@ -40,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">dereita</string>
|
||||
<string name="mousepad_triple_default">medio</string>
|
||||
<string name="mousepad_sensitivity_default">predeterminado</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>O máis lento</item>
|
||||
<item>Lento</item>
|
||||
<item>Predeterminado</item>
|
||||
<item>Rápido</item>
|
||||
<item>O mais rápido</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivos conectados</string>
|
||||
<string name="category_not_paired_devices">Dispositivos dispoñíbeis</string>
|
||||
<string name="category_remembered_devices">Dispositivos coñecidos</string>
|
||||
@@ -55,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Configuración do engadido</string>
|
||||
<string name="device_menu_unpair">Desemparellarse</string>
|
||||
<string name="device_not_reachable">O dispositivo emparellado está fóra do alcance.</string>
|
||||
<string name="pair_new_device">Emparellar cun novo dispositivo</string>
|
||||
<string name="unknown_device">Dispositivo descoñecido</string>
|
||||
<string name="error_not_reachable">Dispositivo fóra do alcance</string>
|
||||
<string name="error_already_requested">Xa solicitou emparellarse.</string>
|
||||
@@ -150,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Enviar unha mensaxe de texto</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensaxes de texto desde un computador de escritorio.</string>
|
||||
<string name="plugin_not_supported">O dispositivo non é compatíbel con este complemento.</string>
|
||||
<string name="findmyphone_title">Atopar o móbil</string>
|
||||
<string name="findmyphone_description">Reproduce un son de chamada no móbil para que poida atopalo.</string>
|
||||
<string name="findmyphone_found">Atopado</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="close">Pechar</string>
|
||||
</resources>
|
||||
|
@@ -25,13 +25,6 @@
|
||||
<item/>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Csatlakoztatott eszközök</string>
|
||||
<string name="category_remembered_devices">Megjegyzett eszközök</string>
|
||||
<string name="plugins_failed_to_load">A bővítményeket nem sikerült betölteni (érintse meg a további információkért):</string>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Controlli multimediali</string>
|
||||
<string name="pref_plugin_mpris_desc">Controlla la riproduzione audio/video dal telefono</string>
|
||||
<string name="pref_plugin_runcommand">Esegui comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Esegui comandi remoti dal tuo telefono</string>
|
||||
<string name="pref_plugin_runcommand_desc">Esegue un comando sul tuo sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Invia e ricevi ping</string>
|
||||
<string name="pref_plugin_notifications">Sincronizzazione notifiche</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Muovi un dito sullo schermo per spostare il puntatore del mouse. Tocca per un clic e usa due/tre dita per i pulsanti destro e centrale. Utilizza una pressione lunga per trascinare e rilasciare.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Imposta azione per il tocco a due dita</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Imposta azione per il tocco a tre dita</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Imposta la sensibilità del touchpad</string>
|
||||
<string name="mousepad_scroll_direction_title">Inverti direzione di scorrimento</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Clic destro</item>
|
||||
<item>Clic centrale</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">destra</string>
|
||||
<string name="mousepad_triple_default">centro</string>
|
||||
<string name="mousepad_sensitivity_default">predefinita</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Minima</item>
|
||||
<item>Più veloce</item>
|
||||
<item>Predefinita</item>
|
||||
<item>Superiore a predefinita</item>
|
||||
<item>Massima</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivi connessi</string>
|
||||
<string name="category_not_paired_devices">Dispositivi disponibili</string>
|
||||
<string name="category_remembered_devices">Dispositivi memorizzati</string>
|
||||
@@ -56,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Impostazioni estensioni</string>
|
||||
<string name="device_menu_unpair">Disassocia</string>
|
||||
<string name="device_not_reachable">Dispositivo associato non raggiungibile</string>
|
||||
<string name="pair_new_device">Associa nuovo dispositivo</string>
|
||||
<string name="unknown_device">Dispositivo sconosciuto</string>
|
||||
<string name="error_not_reachable">Dispositivo fuori portata</string>
|
||||
<string name="error_already_requested">Richiesta già inviata</string>
|
||||
@@ -66,10 +55,6 @@
|
||||
<string name="error_canceled_by_user">Annullata dall\'utente</string>
|
||||
<string name="error_canceled_by_other_peer">Annullata dal dispositivo remoto</string>
|
||||
<string name="error_invalid_key">Ricevuta chiave non valida</string>
|
||||
<string name="encryption_info_title">Informazioni di cifratura</string>
|
||||
<string name="encryption_info_msg_no_ssl">L\'altro dispositivo non utilizza una versione recente di KDE Connect, utilizzando il metodo di cifratura precedente.</string>
|
||||
<string name="my_device_fingerprint">L\'impronta digitale SHA1 del certificato di dispositivo è:</string>
|
||||
<string name="remote_device_fingerprint">L\'impronta digitale SHA1 del certificato di dispositivo remoto è:</string>
|
||||
<string name="pair_requested">Richiesta di associazione</string>
|
||||
<string name="pairing_request_from">Richiesta associazione da %1s</string>
|
||||
<string name="received_url_title">Collegamento ricevuto da %1s</string>
|
||||
@@ -158,6 +143,4 @@
|
||||
<string name="findmyphone_title">Trova il mio telefono</string>
|
||||
<string name="findmyphone_description">Fa squillare questo telefono per trovarlo.</string>
|
||||
<string name="findmyphone_found">Trovato</string>
|
||||
<string name="open">Apri</string>
|
||||
<string name="close">Chiudi</string>
|
||||
</resources>
|
||||
|
@@ -5,13 +5,6 @@
|
||||
<item>Middle click</item>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 seconds</item>
|
||||
<item>20 seconds</item>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">멀티미디어 제어</string>
|
||||
<string name="pref_plugin_mpris_desc">휴대폰에서 오디오/비디오 제어</string>
|
||||
<string name="pref_plugin_runcommand">명령 실행</string>
|
||||
<string name="pref_plugin_runcommand_desc">시스템에서 명령 실행</string>
|
||||
<string name="pref_plugin_ping">핑</string>
|
||||
<string name="pref_plugin_ping_desc">핑 보내고 받기</string>
|
||||
<string name="pref_plugin_notifications">알림 동기화</string>
|
||||
@@ -38,13 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">오른쪽</string>
|
||||
<string name="mousepad_triple_default">가운데</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">연결된 장치</string>
|
||||
<string name="category_not_paired_devices">사용 가능한 장치</string>
|
||||
<string name="category_remembered_devices">기억하는 장치</string>
|
||||
@@ -146,8 +140,7 @@
|
||||
<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>
|
||||
|
@@ -23,13 +23,6 @@
|
||||
<item>Middle click</item>
|
||||
<item>Nieko</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Prijungti įrenginiai</string>
|
||||
<string name="category_not_paired_devices">Pasiekiami įrenginiai</string>
|
||||
<string name="category_remembered_devices">Įsimintieji įrenginiai</string>
|
||||
@@ -41,11 +34,14 @@
|
||||
<string name="error_already_requested">Jau paprašyta suporuoti</string>
|
||||
<string name="error_already_paired">Įrenginys jau suporuotas</string>
|
||||
<string name="error_could_not_send_package">Nepavyksta išsiųsti paketo</string>
|
||||
<string name="error_timed_out">Skirtasis laikas baigėsi</string>
|
||||
<string name="error_canceled_by_user">Naudotojas atšaukė užduotį</string>
|
||||
<string name="error_canceled_by_other_peer">Porininkas atšaukė užduotį</string>
|
||||
<string name="error_invalid_key">Gautas netinkamas raktas</string>
|
||||
<string name="pair_requested">Paprašyta suporuoti</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Norėdami atsakyti, palieskite</string>
|
||||
<string name="reconnect">Prisijungti iš naujo</string>
|
||||
<string name="show_keyboard">Rodyti klaviatūrą</string>
|
||||
@@ -83,6 +79,7 @@
|
||||
<string name="pair_device_action">Suporuoti naują įrenginį</string>
|
||||
<string name="unpair_device_action">Atrišti %s</string>
|
||||
<string name="custom_device_list">Pridėti įrenginį pagal IP</string>
|
||||
<string name="sftp_internal_storage">Vidinė saugykla</string>
|
||||
<string name="sftp_all_files">Visi failai</string>
|
||||
<string name="sftp_sdcard_num">SD kortelė %d</string>
|
||||
<string name="sftp_sdcard">SD kortelė</string>
|
||||
@@ -96,13 +93,11 @@
|
||||
<string name="pairing_description">Čia turėtų pasirodyti to kiti paties tinklo įrenginiai, kuriuose veikia „KDE Connect“</string>
|
||||
<string name="device_paired">Įrenginys suporuotas</string>
|
||||
<string name="device_rename_title">Pervadinti įrenginį</string>
|
||||
<string name="device_rename_confirm">Pervadinti</string>
|
||||
<string name="refresh">Atnaujinti</string>
|
||||
<string name="unreachable_description">Šis suporuotas įrenginys nepasiekiamas. Patikrinkite, ar jis prisijungęs prie to paties tinklo.</string>
|
||||
<string name="pref_plugin_telepathy">Siųsti SMS</string>
|
||||
<string name="plugin_not_supported">Telefonas nepalaiko šio papildinio</string>
|
||||
<string name="findmyphone_title">Rasti telefoną</string>
|
||||
<string name="findmyphone_description">Telefonas skambės, tad galėsite jį rasti.</string>
|
||||
<string name="findmyphone_found">Radau</string>
|
||||
<string name="open">Atverti</string>
|
||||
<string name="close">Užverti</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Bediening van multimedia</string>
|
||||
<string name="pref_plugin_mpris_desc">Bedien de audio/video vanaf uw telefoon</string>
|
||||
<string name="pref_plugin_runcommand">Commando uitvoeren</string>
|
||||
<string name="pref_plugin_runcommand_desc">Commando\'s afvuren op afstand vanaf uw telefoon</string>
|
||||
<string name="pref_plugin_runcommand_desc">Voert een commando uit op uw systeem</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Pings verzenden en ontvangen</string>
|
||||
<string name="pref_plugin_notifications">Synchronisatie van meldingen</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Veeg met een vinger op het scherm om de muiscursor te verplaatsen. Tik om te klikken en gebruik twee/drie vingers voor rechter en middelste knop. Druk lang voor slepen en loslaten.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Tikactie met twee vingers instellen</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Tikactie met drie vingers instellen</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Gevoeligheid van touchpad instellen</string>
|
||||
<string name="mousepad_scroll_direction_title">Schuifrichting omdraaien</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Rechter muisklik</item>
|
||||
<item>Middelste muisklik</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">rechts</string>
|
||||
<string name="mousepad_triple_default">midden</string>
|
||||
<string name="mousepad_sensitivity_default">standaard</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Langzaamst</item>
|
||||
<item>Langzaam</item>
|
||||
<item>Standaard</item>
|
||||
<item>Boven standaard</item>
|
||||
<item>Snelste</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Verbonden apparaten</string>
|
||||
<string name="category_not_paired_devices">Beschikbare apparaten</string>
|
||||
<string name="category_remembered_devices">Onthouden apparaten</string>
|
||||
@@ -66,10 +56,6 @@
|
||||
<string name="error_canceled_by_user">Geannuleerd door gebruiker</string>
|
||||
<string name="error_canceled_by_other_peer">Geannuleerd door andere kant</string>
|
||||
<string name="error_invalid_key">Ongeldige sleutel ontvangen</string>
|
||||
<string name="encryption_info_title">Versleutelde informatie</string>
|
||||
<string name="encryption_info_msg_no_ssl">Het andere apparaat gebruikt geen recente versie van KDE Connect, de verouderde versleutelingsmethode zal worden gebruikt.</string>
|
||||
<string name="my_device_fingerprint">De SHA1 vingerafdruk van het certificaat van uw apparaat is:</string>
|
||||
<string name="remote_device_fingerprint">De SHA1 vingerafdruk van het certificaat van het apparaat op afstand is:</string>
|
||||
<string name="pair_requested">Paar gevraagd</string>
|
||||
<string name="pairing_request_from">Verzoek om een paar te maken van %1s</string>
|
||||
<string name="received_url_title">Ontvangen koppeling van %1s</string>
|
||||
@@ -158,6 +144,4 @@
|
||||
<string name="findmyphone_title">Zoek mijn telefoon</string>
|
||||
<string name="findmyphone_description">Laat deze telefoon bellen zodat u het kunt vinden.</string>
|
||||
<string name="findmyphone_found">Gevonden</string>
|
||||
<string name="open">Openen</string>
|
||||
<string name="close">Sluiten</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Sterowanie multimediami</string>
|
||||
<string name="pref_plugin_mpris_desc">Steruj dźwiękiem/obrazem ze swojego telefonu</string>
|
||||
<string name="pref_plugin_runcommand">Wykonaj polecenie</string>
|
||||
<string name="pref_plugin_runcommand_desc">Wyzwalaj zdalne polecenia z twojego telefonu</string>
|
||||
<string name="pref_plugin_runcommand_desc">Wykonuje polecenie na twoim systmie</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Wysyłaj i otrzymuj pingi</string>
|
||||
<string name="pref_plugin_notifications">Powiadomienia synchronizacji</string>
|
||||
@@ -32,23 +32,13 @@
|
||||
<string name="mousepad_info">Przesuń palcem po ekranie, aby przesunąć wskaźnik myszy. Stuknij, aby wywołać naciśniecie lewym przyciskiem myszy i użyj dwóch/trzech palców, aby wywołać naciśniecie prawym i środkowym przyciskiem myszy. Przyciśnij na dłużej, aby przeciągnąć i upuścić.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Ustaw działanie po dwukrotnym stuknięciu palcem</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Ustaw działanie po trzykrotnym stuknięciu palcem</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Ustaw czułość gładzika</string>
|
||||
<string name="mousepad_scroll_direction_title">Odwróć stronę przewijania</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Kliknięcie prawym</item>
|
||||
<item>Kliknięcie środkowym</item>
|
||||
<item>Naciśnięcie prawym</item>
|
||||
<item>Naciśnięcie środkowym</item>
|
||||
<item>Nic</item>
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">prawo</string>
|
||||
<string name="mousepad_triple_default">środek</string>
|
||||
<string name="mousepad_sensitivity_default">domyślne</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Najmniejsza</item>
|
||||
<item>Ponad najmniejszą</item>
|
||||
<item>Domyślna</item>
|
||||
<item>Powyżej domyślnej</item>
|
||||
<item>Największa</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Podłączone urządzenia</string>
|
||||
<string name="category_not_paired_devices">Dostępne urządzenia</string>
|
||||
<string name="category_remembered_devices">Zapamiętane urządzenia</string>
|
||||
@@ -56,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Ustawienia wtyczki</string>
|
||||
<string name="device_menu_unpair">Odparuj</string>
|
||||
<string name="device_not_reachable">Sparowane urządzenie nieosiągalne</string>
|
||||
<string name="pair_new_device">Sparuj nowe urządzenie</string>
|
||||
<string name="unknown_device">Nieznane urządzenie</string>
|
||||
<string name="error_not_reachable">Urządzenie nieosiągalne</string>
|
||||
<string name="error_already_requested">Już zażądano parowania</string>
|
||||
@@ -66,8 +55,6 @@
|
||||
<string name="error_canceled_by_user">Anulowane przez użytkownika</string>
|
||||
<string name="error_canceled_by_other_peer">Anulowane przez innego partnera</string>
|
||||
<string name="error_invalid_key">Otrzymano nieprawidłowy klucz</string>
|
||||
<string name="encryption_info_title">Zaszyfrowane informacje</string>
|
||||
<string name="encryption_info_msg_no_ssl">Drugie urządzenie nie używa ostatniej wersji KDE Connect, użyto przestarzałego szyfrowania.</string>
|
||||
<string name="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>
|
||||
@@ -156,6 +143,4 @@
|
||||
<string name="findmyphone_title">Znajdź mój telefon</string>
|
||||
<string name="findmyphone_description">Dzwoni na ten telefon, tak abyś mógł go znaleźć.</string>
|
||||
<string name="findmyphone_found">Znaleziony</string>
|
||||
<string name="open">Otwórz</string>
|
||||
<string name="close">Zamknij</string>
|
||||
</resources>
|
||||
|
@@ -1,24 +1,25 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="pref_plugin_telephony">Notificação de telefonia</string>
|
||||
<string name="pref_plugin_telephony">Notificação telefônica</string>
|
||||
<string name="pref_plugin_telephony_desc">Envia notificações de SMS e chamadas</string>
|
||||
<string name="pref_plugin_battery">Relatório da bateria</string>
|
||||
<string name="pref_plugin_battery_desc">Informação periódica do status da bateria</string>
|
||||
<string name="pref_plugin_battery_desc">Informa o status da bateria periodicamente</string>
|
||||
<string name="pref_plugin_sftp">Exposição do sistema de arquivos</string>
|
||||
<string name="pref_plugin_sftp_desc">Navegação remota pelo sistema de arquivos do telefone</string>
|
||||
<string name="pref_plugin_clipboard">Sincronização da área de transferência</string>
|
||||
<string name="pref_plugin_sftp_desc">Permite navegar remotamente pelo sistema de arquivos telefone</string>
|
||||
<string name="pref_plugin_clipboard">Sincronizar área de transferência</string>
|
||||
<string name="pref_plugin_clipboard_desc">Compartilha o conteúdo da área de transferência</string>
|
||||
<string name="pref_plugin_mousepad">Introdução de dados remota</string>
|
||||
<string name="pref_plugin_mousepad_desc">Use seu telefone como mouse ou teclado</string>
|
||||
<string name="pref_plugin_mousepad_desc">Usar seu telefone como mouse ou teclado</string>
|
||||
<string name="pref_plugin_mpris">Controle multimídia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controla áudio e vídeo a partir do seu telefone</string>
|
||||
<string name="pref_plugin_runcommand">Executar comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa um comando no seu sistema</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Envia e recebe pings</string>
|
||||
<string name="pref_plugin_notifications">Sincronização de notificações</string>
|
||||
<string name="pref_plugin_notifications_desc">Acesse suas notificações a partir de outros dispositivos</string>
|
||||
<string name="pref_plugin_sharereceiver">Compartilhar e receber</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Compartilha arquivos e URLs entre os dispositivos</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Compartilhar arquivos e URLs entre os dispositivos</string>
|
||||
<string name="plugin_not_available">Esta funcionalidade não está disponível na sua versão do Android</string>
|
||||
<string name="device_list_empty">Sem dispositivos</string>
|
||||
<string name="ok">OK</string>
|
||||
@@ -28,11 +29,9 @@
|
||||
<string name="send_ping">Enviar ping</string>
|
||||
<string name="open_mpris_controls">Controle multimídia</string>
|
||||
<string name="open_mousepad">Introdução de dados remota</string>
|
||||
<string name="mousepad_info">Mova um dedo pela tela para mover o ponteiro do mouse. Dê um toque para clicar e use dois/três dedos para os botões da direita e do meio. Use uma pressão longa para arrastar e soltar.</string>
|
||||
<string name="mousepad_info">Mova um dedo pelo tela para mover o ponteiro do mouse. Dê um toque para clicar e use dois/três dedos para os botões da direita e do meio. Use uma pressão longa para arrastar e soltar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Definir a ação do toque com dois dedos</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Definir a ação do toque com três dedos</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do touchpad</string>
|
||||
<string name="mousepad_scroll_direction_title">Direção de rolagem inversa</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Botão direito</item>
|
||||
<item>Botão do meio</item>
|
||||
@@ -40,22 +39,13 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">direita</string>
|
||||
<string name="mousepad_triple_default">meio</string>
|
||||
<string name="mousepad_sensitivity_default">padrão</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Mais lento</item>
|
||||
<item>Ainda mais lento</item>
|
||||
<item>Padrão</item>
|
||||
<item>Acima do padrão</item>
|
||||
<item>Mais rápido</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivos conectados</string>
|
||||
<string name="category_not_paired_devices">Dispositivos disponíveis</string>
|
||||
<string name="category_remembered_devices">Dispositivos lembrados</string>
|
||||
<string name="plugins_failed_to_load">Plugins não carregados (toque para mais informações):</string>
|
||||
<string name="device_menu_plugins">Configuração dos plugins</string>
|
||||
<string name="device_menu_plugins">Configurações do plugin</string>
|
||||
<string name="device_menu_unpair">Cancelar emparelhamento</string>
|
||||
<string name="device_not_reachable">O dispositivo pareado está inacessível</string>
|
||||
<string name="pair_new_device">Emparelhar novo dispositivo</string>
|
||||
<string name="unknown_device">Dispositivo desconhecido</string>
|
||||
<string name="error_not_reachable">Dispositivo inacessível</string>
|
||||
<string name="error_already_requested">O emparelhamento já foi solicitado</string>
|
||||
@@ -65,8 +55,8 @@
|
||||
<string name="error_canceled_by_user">Cancelado pelo usuário</string>
|
||||
<string name="error_canceled_by_other_peer">Cancelado pelo outro dispositivo</string>
|
||||
<string name="error_invalid_key">Chave inválida recebida</string>
|
||||
<string name="pair_requested">Solicitação de emparelhamento</string>
|
||||
<string name="pairing_request_from">Emparelhamento solicitado por %1s</string>
|
||||
<string name="pair_requested">Emparelhamento solicitado</string>
|
||||
<string name="pairing_request_from">Emparelhando solicitação de %1s</string>
|
||||
<string name="received_url_title">Link recebido de %1s</string>
|
||||
<string name="received_url_text">Toque para abrir o \'%1s\'</string>
|
||||
<string name="incoming_file_title">Arquivo recebido de %1s</string>
|
||||
@@ -121,12 +111,12 @@
|
||||
<string name="shareplugin_text_saved">Texto recebido e salvo na área de transferência</string>
|
||||
<string name="custom_devices_settings">Lista de dispositivos personalizada</string>
|
||||
<string name="pair_device_action">Emparelhar um novo dispositivo</string>
|
||||
<string name="unpair_device_action">Desemparelhar %s</string>
|
||||
<string name="unpair_device_action">Desemparelhar o %s</string>
|
||||
<string name="custom_device_list">Adicionar dispositivos pelo IP</string>
|
||||
<string name="share_notification_preference">Notificações sonoras</string>
|
||||
<string name="share_notification_preference_summary">Vibra e reproduz um som ao receber um arquivo</string>
|
||||
<string name="share_notification_preference">Notificações barulhentas</string>
|
||||
<string name="share_notification_preference_summary">Vibrar e tocar um som ao receber um arquivo</string>
|
||||
<string name="title_activity_notification_filter">Filtro de notificações</string>
|
||||
<string name="filter_apps_info">As notificações dos aplicativos selecionados serão sincronizadas.</string>
|
||||
<string name="filter_apps_info">As notificações serão sincronizadas para os aplicativos selecionados.</string>
|
||||
<string name="sftp_internal_storage">Armazenamento interno</string>
|
||||
<string name="sftp_all_files">Todos os arquivos</string>
|
||||
<string name="sftp_sdcard_num">Cartão SD %d</string>
|
||||
@@ -140,7 +130,7 @@
|
||||
<string name="mpris_player_on_device">%1$s em %2$s</string>
|
||||
<string name="send_files">Enviar arquivos</string>
|
||||
<string name="pairing_title">Dispositivos do KDE Connect</string>
|
||||
<string name="pairing_description">Outros dispositivos executando o KDE Connect na mesma rede devem aparecer aqui.</string>
|
||||
<string name="pairing_description">Os outros dispositivos executando o KDE Connect na mesma rede devem aparecer aqui.</string>
|
||||
<string name="device_paired">Dispositivo emparelhado</string>
|
||||
<string name="device_rename_title">Renomear dispositivo</string>
|
||||
<string name="device_rename_confirm">Renomear</string>
|
||||
@@ -150,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Enviar SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensagens de texto do seu Desktop</string>
|
||||
<string name="plugin_not_supported">Este plugin não é suportado pelo dispositivo</string>
|
||||
<string name="findmyphone_title">Encontrar meu telefone</string>
|
||||
<string name="findmyphone_description">Faça este telefone tocar para encontrá-lo.</string>
|
||||
<string name="findmyphone_found">Encontrado</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="close">Fechar</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Comandos multimédia</string>
|
||||
<string name="pref_plugin_mpris_desc">Controlar o áudio/vídeo a partir do seu telefone</string>
|
||||
<string name="pref_plugin_runcommand">Executar um Comando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Despoletar comandos remotos a partir do seu telefone</string>
|
||||
<string name="pref_plugin_runcommand_desc">Executa um comando no seu sistema</string>
|
||||
<string name="pref_plugin_ping">Contacto</string>
|
||||
<string name="pref_plugin_ping_desc">Enviar e receber pedidos de contacto (\'ping\')</string>
|
||||
<string name="pref_plugin_notifications">Sincronização da notificação</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Mova um dedo pelo ecrã para mover o cursor do rato. Dê um toque para carregar no botão esquerdo e use dois/três dedos para os botões direito e do meio. Use uma pressão longa para arrastar e largar.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Definir a acção do toque com dois dedos</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Definir a acção do toque com três dedos</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do rato por toque</string>
|
||||
<string name="mousepad_scroll_direction_title">Direcção de Deslocamento Inversa</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Botão direito</item>
|
||||
<item>Botão do meio</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">direita</string>
|
||||
<string name="mousepad_triple_default">meio</string>
|
||||
<string name="mousepad_sensitivity_default">predefinição</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Mais Lento</item>
|
||||
<item>Ainda Mais Lento</item>
|
||||
<item>Predefinição</item>
|
||||
<item>Acima da Predefinição</item>
|
||||
<item>Mais Rápido</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivos ligados</string>
|
||||
<string name="category_not_paired_devices">Dispositivos disponíveis</string>
|
||||
<string name="category_remembered_devices">Dispositivos recordados</string>
|
||||
@@ -56,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Configuração do \'plugin\'</string>
|
||||
<string name="device_menu_unpair">Desemparelhar</string>
|
||||
<string name="device_not_reachable">O dispositivo emparelhado está inacessível</string>
|
||||
<string name="pair_new_device">Emparelhar um novo dispositivo</string>
|
||||
<string name="unknown_device">Dispositivo desconhecido</string>
|
||||
<string name="error_not_reachable">Dispositivo inacessível</string>
|
||||
<string name="error_already_requested">O emparelhamento já foi pedido</string>
|
||||
@@ -66,10 +55,6 @@
|
||||
<string name="error_canceled_by_user">Cancelado pelo utilizador</string>
|
||||
<string name="error_canceled_by_other_peer">Cancelado pela outra máquina</string>
|
||||
<string name="error_invalid_key">Chave inválida recebida</string>
|
||||
<string name="encryption_info_title">Dados de Encriptação</string>
|
||||
<string name="encryption_info_msg_no_ssl">O outro dispositivo não usa uma versão recente do KDE Connect; será usado o método antigo de encriptação.</string>
|
||||
<string name="my_device_fingerprint">A impressão digital SHA1 do certificado do seu dispositivo é:</string>
|
||||
<string name="remote_device_fingerprint">A impressão digital SHA1 do certificado do dispositivo remoto é:</string>
|
||||
<string name="pair_requested">Emparelhamento pedido</string>
|
||||
<string name="pairing_request_from">Pedido de emparelhamento de %1s</string>
|
||||
<string name="received_url_title">Ligação recebida de %1s</string>
|
||||
@@ -155,9 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Enviar um SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Enviar mensagens de texto a partir do seu ambiente de trabalho</string>
|
||||
<string name="plugin_not_supported">Este \'plugin\' não é suportado pelo dispositivo</string>
|
||||
<string name="findmyphone_title">Descobrir o meu telefone</string>
|
||||
<string name="findmyphone_title">Descobrir o Meu Telefone</string>
|
||||
<string name="findmyphone_description">Toca este telefone para que o possa encontrar.</string>
|
||||
<string name="findmyphone_found">Encontrado</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="close">Fechar</string>
|
||||
</resources>
|
||||
|
@@ -25,13 +25,6 @@
|
||||
<item>Middle click</item>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispozitive conectate</string>
|
||||
<string name="category_remembered_devices">Dispozitive memorizate</string>
|
||||
<string name="plugins_failed_to_load">Încărcarea extensiilor a eșuat (atingeți pentru mai multe informații):</string>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Управление воспроизведением</string>
|
||||
<string name="pref_plugin_mpris_desc">Управление медиапроигрывателем с телефона</string>
|
||||
<string name="pref_plugin_runcommand">Запуск команд</string>
|
||||
<string name="pref_plugin_runcommand_desc">Выполнение команд на вашем компьютере</string>
|
||||
<string name="pref_plugin_ping">Пинг</string>
|
||||
<string name="pref_plugin_ping_desc">Отправка и получение тестовых сигналов</string>
|
||||
<string name="pref_plugin_notifications">Синхронизация уведомлений</string>
|
||||
@@ -38,13 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">Нажатие правой кнопки</string>
|
||||
<string name="mousepad_triple_default">Нажатие средней кнопки</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Подключённые устройства</string>
|
||||
<string name="category_not_paired_devices">Доступные устройства</string>
|
||||
<string name="category_remembered_devices">Известные устройства</string>
|
||||
@@ -146,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">Отправка SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Отправка SMS-сообщений с вашего компьютера</string>
|
||||
<string name="plugin_not_supported">Этот модуль не поддерживается устройством</string>
|
||||
<string name="findmyphone_title">Поиск телефона</string>
|
||||
<string name="findmyphone_description">Подача звукового сигнала на телефоне, чтобы вы могли его найти.</string>
|
||||
<string name="findmyphone_found">Найден</string>
|
||||
<string name="open">Открыть</string>
|
||||
<string name="close">Закрыть</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimediálne ovládače</string>
|
||||
<string name="pref_plugin_mpris_desc">Ovládať audio/video z vášho telefónu</string>
|
||||
<string name="pref_plugin_runcommand">Spustiť príkaz</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustiť vzdialené príkazy z vášho telefónu</string>
|
||||
<string name="pref_plugin_runcommand_desc">Spustí príkazy na vašom systéme</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Poslať a prijať pingy</string>
|
||||
<string name="pref_plugin_notifications">Synchronizácia pripomienok</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Posúvajte prst na obrazovke na posun kurzora. Ťuknutie vyvolá klik a použite dva/tri prsty pre pravé a stredné tlačidlo. Použite dlhé stlačenie pre drag and drop.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Nastaviť akciu dvoch prstov</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Nastaviť akciu troch prstov</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Nastaviť citlivosť touchpadu</string>
|
||||
<string name="mousepad_scroll_direction_title">Obrátený smer rolovania</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Kliknutie pravým tlačidlom</item>
|
||||
<item>Stredný klik</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">vpravo</string>
|
||||
<string name="mousepad_triple_default">stred</string>
|
||||
<string name="mousepad_sensitivity_default">predvolené</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Najpomalšie</item>
|
||||
<item>Nad najpomalším</item>
|
||||
<item>Predvolené</item>
|
||||
<item>Nad priemerným</item>
|
||||
<item>Najrýchlejšie</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Pripojené zariadenia</string>
|
||||
<string name="category_not_paired_devices">Dostupné zariadenia</string>
|
||||
<string name="category_remembered_devices">Zapamätané zariadenia</string>
|
||||
@@ -56,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Nastavenia pluginu</string>
|
||||
<string name="device_menu_unpair">Odpárovať</string>
|
||||
<string name="device_not_reachable">Spárované zariadenie nedostupné</string>
|
||||
<string name="pair_new_device">Spárovať nové zariadenie</string>
|
||||
<string name="unknown_device">Neznáme zariadenie</string>
|
||||
<string name="error_not_reachable">Zariadenie nedostupné</string>
|
||||
<string name="error_already_requested">Spárovanie už vyžiadané</string>
|
||||
@@ -66,10 +55,6 @@
|
||||
<string name="error_canceled_by_user">Zrušené používateľom</string>
|
||||
<string name="error_canceled_by_other_peer">Zrušené iným klientom</string>
|
||||
<string name="error_invalid_key">Získaný nesprávny kľúč</string>
|
||||
<string name="encryption_info_title">Informácia o šifrovaní</string>
|
||||
<string name="encryption_info_msg_no_ssl">Ďalšie zariadenie nepoužíva aktuálnu verziu KDE Connect, používam klasickú metódu šifrovania.</string>
|
||||
<string name="my_device_fingerprint">Odtlačok SHA1 vášho certifikátu zariadenia je:</string>
|
||||
<string name="remote_device_fingerprint">Odtlačok SHA1 vzdialeného certifikátu zariadenia je:</string>
|
||||
<string name="pair_requested">Spárovanie vyžiadané</string>
|
||||
<string name="pairing_request_from">Požiadavka na spárovanie od %1s</string>
|
||||
<string name="received_url_title">Prijatý odkaz od %1s</string>
|
||||
@@ -158,6 +143,4 @@
|
||||
<string name="findmyphone_title">Nájsť môj telefón</string>
|
||||
<string name="findmyphone_description">Prezvoní vaše zariadenie, aby ste ho našli.</string>
|
||||
<string name="findmyphone_found">Nájdené</string>
|
||||
<string name="open">Otvoriť</string>
|
||||
<string name="close">Zavrieť</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Multimediakontroller</string>
|
||||
<string name="pref_plugin_mpris_desc">Styr ljud och video från telefonen</string>
|
||||
<string name="pref_plugin_runcommand">Kör kommando</string>
|
||||
<string name="pref_plugin_runcommand_desc">Utlös fjärrkommandon från din telefon</string>
|
||||
<string name="pref_plugin_runcommand_desc">Kör ett kommando på systemet</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Skicka och ta emot ping</string>
|
||||
<string name="pref_plugin_notifications">Synkronisering av underrättelser</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<string name="mousepad_info">Flytta fingret på skärmen för att röra muspekaren. Rör för att klicka, och använd två eller tre fingrar för höger- och mittenknapparna. Använd en längre beröring för drag och släpp.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Ställ in åtgärd vid två fingerberöringar</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Ställ in åtgärd vid tre fingerberöringar</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Ställ in tryckplattans känslighet</string>
|
||||
<string name="mousepad_scroll_direction_title">Omvänd rullningsriktning</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Högerklick</item>
|
||||
<item>Mittenklick</item>
|
||||
@@ -41,14 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">höger</string>
|
||||
<string name="mousepad_triple_default">mitten</string>
|
||||
<string name="mousepad_sensitivity_default">normal</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Långsammaste</item>
|
||||
<item>Ovanför långsammaste</item>
|
||||
<item>Normal</item>
|
||||
<item>Ovanför normal</item>
|
||||
<item>Snabbaste</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Anslutna apparater</string>
|
||||
<string name="category_not_paired_devices">Tillgängliga apparater</string>
|
||||
<string name="category_remembered_devices">Ihågkomna apparater</string>
|
||||
@@ -56,7 +46,6 @@
|
||||
<string name="device_menu_plugins">Inställningar av insticksprogram</string>
|
||||
<string name="device_menu_unpair">Ta bort ihopparning</string>
|
||||
<string name="device_not_reachable">Ihopparad apparat kan inte nås</string>
|
||||
<string name="pair_new_device">Para ihop ny apparat</string>
|
||||
<string name="unknown_device">Okänd apparat</string>
|
||||
<string name="error_not_reachable">Apparaten kan inte nås</string>
|
||||
<string name="error_already_requested">Ihopparning redan begärd</string>
|
||||
@@ -66,10 +55,6 @@
|
||||
<string name="error_canceled_by_user">Avbruten av användaren</string>
|
||||
<string name="error_canceled_by_other_peer">Avbruten av motparten</string>
|
||||
<string name="error_invalid_key">Ogiltig nyckel mottagen</string>
|
||||
<string name="encryption_info_title">Krypteringsinformation</string>
|
||||
<string name="encryption_info_msg_no_ssl">Den andra apparaten använder inte en aktuell version av KDE-anslut. Använder den föråldrade krypteringsmetoden.</string>
|
||||
<string name="my_device_fingerprint">SHA1-fingeravtryck för din apparats certifikat är:</string>
|
||||
<string name="remote_device_fingerprint">SHA1-fingeravtryck för den andra apparatens certifikat är:</string>
|
||||
<string name="pair_requested">Ihopparning begärd</string>
|
||||
<string name="pairing_request_from">Begäran om ihopparning från %1s</string>
|
||||
<string name="received_url_title">Tog emot länk från %1s</string>
|
||||
@@ -158,6 +143,4 @@
|
||||
<string name="findmyphone_title">Hitta min telefon</string>
|
||||
<string name="findmyphone_description">Ringer till telefonen så att du kan hitta den.</string>
|
||||
<string name="findmyphone_found">Hittade den</string>
|
||||
<string name="open">Öppna</string>
|
||||
<string name="close">Stäng</string>
|
||||
</resources>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<string name="pref_plugin_mpris">Керування відтворенням</string>
|
||||
<string name="pref_plugin_mpris_desc">Керування відтворенням звуку та відео з вашого телефону</string>
|
||||
<string name="pref_plugin_runcommand">Виконати команду</string>
|
||||
<string name="pref_plugin_runcommand_desc">Віддалені команди з вашого телефону</string>
|
||||
<string name="pref_plugin_runcommand_desc">Виконує команду у вашій системі</string>
|
||||
<string name="pref_plugin_ping">Підтримання зв’язку</string>
|
||||
<string name="pref_plugin_ping_desc">Надсилання і отримання сигналів підтримання зв’язку</string>
|
||||
<string name="pref_plugin_notifications">Синхронізація сповіщень</string>
|
||||
@@ -32,8 +32,6 @@
|
||||
<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>
|
||||
@@ -41,14 +39,6 @@
|
||||
</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>
|
||||
@@ -66,10 +56,6 @@
|
||||
<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>
|
||||
@@ -158,6 +144,4 @@
|
||||
<string name="findmyphone_title">Знайти телефон</string>
|
||||
<string name="findmyphone_description">Відтворити дзвінок, щоб телефон було простіше знайти.</string>
|
||||
<string name="findmyphone_found">Знайдено</string>
|
||||
<string name="open">Відкрити</string>
|
||||
<string name="close">Закрити</string>
|
||||
</resources>
|
||||
|
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectThemeBase.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_mpris">多媒体控制</string>
|
||||
<string name="pref_plugin_mpris_desc">从手机控制音频或视频</string>
|
||||
<string name="pref_plugin_runcommand">执行命令</string>
|
||||
<string name="pref_plugin_runcommand_desc">在您的系统上执行命令</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">发送和接受ping</string>
|
||||
<string name="pref_plugin_notifications">通知同步</string>
|
||||
@@ -38,13 +39,6 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">右</string>
|
||||
<string name="mousepad_triple_default">中</string>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">已连接设备</string>
|
||||
<string name="category_not_paired_devices">可用设备</string>
|
||||
<string name="category_remembered_devices">已记住设备</string>
|
||||
@@ -52,7 +46,6 @@
|
||||
<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>
|
||||
@@ -147,8 +140,7 @@
|
||||
<string name="pref_plugin_telepathy">发送短消息</string>
|
||||
<string name="pref_plugin_telepathy_desc">从桌面发送短消息</string>
|
||||
<string name="plugin_not_supported">设备不支持此插件</string>
|
||||
<string name="findmyphone_title">找到我的手机</string>
|
||||
<string name="findmyphone_description">让手机响铃从而找到它</string>
|
||||
<string name="findmyphone_found">找到</string>
|
||||
<string name="open">打开</string>
|
||||
<string name="close">关闭</string>
|
||||
</resources>
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<string name="pref_plugin_mpris">Multimedia controls</string>
|
||||
<string name="pref_plugin_mpris_desc">Control audio/video from your phone</string>
|
||||
<string name="pref_plugin_runcommand">Run Command</string>
|
||||
<string name="pref_plugin_runcommand_desc">Trigger remote commands from your phone</string>
|
||||
<string name="pref_plugin_runcommand_desc">Runs a command on your system</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send and receive pings</string>
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
@@ -33,12 +33,8 @@
|
||||
<string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use a long press to drag\'n drop.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Set two finger tap action</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Set three finger tap action</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Set touchpad sensitivity</string>
|
||||
<string name="mousepad_double_tap_key" translatable="false">mousepad_double_tap_key</string>
|
||||
<string name="mousepad_triple_tap_key" translatable="false">mousepad_triple_tap_key</string>
|
||||
<string name="mousepad_sensitivity_key" translatable="false">mousepad_sensitivity_key</string>
|
||||
<string name="mousepad_scroll_direction_title">Reverse Scrolling Direction</string>
|
||||
<string name="mousepad_scroll_direction" translatable="false">mousepad_scroll_direction</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item>Right click</item>
|
||||
<item>Middle click</item>
|
||||
@@ -46,26 +42,11 @@
|
||||
</string-array>
|
||||
<string name="mousepad_double_default">right</string>
|
||||
<string name="mousepad_triple_default">middle</string>
|
||||
<string name="mousepad_sensitivity_default">default</string>
|
||||
<string-array name="mousepad_tap_values" translatable="false">
|
||||
<item>right</item>
|
||||
<item>middle</item>
|
||||
<item>none</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_entries">
|
||||
<item>Slowest</item>
|
||||
<item>Above Slowest</item>
|
||||
<item>Default</item>
|
||||
<item>Above Default</item>
|
||||
<item>Fastest</item>
|
||||
</string-array>
|
||||
<string-array name="mousepad_sensitivity_values" translatable="false">
|
||||
<item>slowest</item>
|
||||
<item>aboveSlowest</item>
|
||||
<item>default</item>
|
||||
<item>aboveDefault</item>
|
||||
<item>fastest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Connected devices</string>
|
||||
<string name="category_not_paired_devices">Available devices</string>
|
||||
<string name="category_remembered_devices">Remembered devices</string>
|
||||
@@ -83,10 +64,6 @@
|
||||
<string name="error_canceled_by_user">Canceled by user</string>
|
||||
<string name="error_canceled_by_other_peer">Canceled by other peer</string>
|
||||
<string name="error_invalid_key">Invalid key received</string>
|
||||
<string name="encryption_info_title">Encryption Info</string>
|
||||
<string name="encryption_info_msg_no_ssl">The other device doesn\'t use a recent version of KDE Connect, using the legacy encryption method.</string>
|
||||
<string name="my_device_fingerprint">SHA1 fingerprint of your device certificate is:</string>
|
||||
<string name="remote_device_fingerprint">SHA1 fingerprint of remote device certificate is:</string>
|
||||
<string name="pair_requested">Pair requested</string>
|
||||
<string name="pairing_request_from">Pairing request from %1s</string>
|
||||
<string name="received_url_title">Received link from %1s</string>
|
||||
@@ -182,10 +159,7 @@
|
||||
<string name="pref_plugin_telepathy">Send SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string>
|
||||
<string name="plugin_not_supported">This plugin is not supported by the device</string>
|
||||
<string name="findmyphone_title">Find my phone</string>
|
||||
<string name="findmyphone_title">Find My Phone</string>
|
||||
<string name="findmyphone_description">Rings this phone so you can find it.</string>
|
||||
<string name="findmyphone_found">Found</string>
|
||||
|
||||
<string name="open">Open</string>
|
||||
<string name="close">Close</string>
|
||||
</resources>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<color name="accent">#4ebffa</color>
|
||||
|
||||
<!-- NoActionBar because we use a Toolbar widget as ActionBar -->
|
||||
<style name="KdeConnectThemeBase" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<style name="KdeConnectTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- The three colors used by system widgets, according to https://chris.banes.me/2014/10/17/appcompat-v21/ -->
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||
@@ -13,15 +13,9 @@
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="KdeConnectThemeBase.NoActionBar" parent="KdeConnectThemeBase">
|
||||
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectTheme">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="KdeConnectTheme" parent="KdeConnectThemeBase">
|
||||
</style>
|
||||
|
||||
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectThemeBase.NoActionBar">
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@@ -21,18 +21,4 @@
|
||||
android:entryValues="@array/mousepad_tap_values"
|
||||
android:defaultValue="@string/mousepad_triple_default" />
|
||||
|
||||
<ListPreference
|
||||
android:id="@+id/mousepad_sensitivity_preference"
|
||||
android:key="@string/mousepad_sensitivity_key"
|
||||
android:title="@string/mousepad_sensitivity_settings_title"
|
||||
android:summary="%s"
|
||||
android:entries="@array/mousepad_sensitivity_entries"
|
||||
android:entryValues="@array/mousepad_sensitivity_values"
|
||||
android:defaultValue="@string/mousepad_sensitivity_default" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:id="@+id/mousepad_scroll_preference"
|
||||
android:key="@string/mousepad_scroll_direction"
|
||||
android:title="@string/mousepad_scroll_direction_title"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceScreen>
|
@@ -20,8 +20,6 @@
|
||||
|
||||
package org.kde.kdeconnect.Backends;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
@@ -32,7 +30,9 @@ import java.util.ArrayList;
|
||||
|
||||
public abstract class BaseLink {
|
||||
|
||||
protected final Context context;
|
||||
public enum ConnectionStarted {
|
||||
Locally, Remotely;
|
||||
};
|
||||
|
||||
public interface PackageReceiver {
|
||||
void onPackageReceived(NetworkPackage np);
|
||||
@@ -43,16 +43,17 @@ public abstract class BaseLink {
|
||||
private final ArrayList<PackageReceiver> receivers = new ArrayList<>();
|
||||
protected PrivateKey privateKey;
|
||||
|
||||
protected BaseLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
|
||||
this.context = context;
|
||||
protected 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.
|
||||
|
||||
protected BaseLink(String deviceId, BaseLinkProvider linkProvider, ConnectionStarted connectionSource) {
|
||||
this.linkProvider = linkProvider;
|
||||
this.deviceId = deviceId;
|
||||
this.connectionSource = connectionSource;
|
||||
}
|
||||
|
||||
/* To be implemented by each link for pairing handlers */
|
||||
public abstract String getName();
|
||||
public abstract BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback);
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
@@ -65,9 +66,8 @@ public abstract class BaseLink {
|
||||
return linkProvider;
|
||||
}
|
||||
|
||||
//The daemon will periodically destroy unpaired links if this returns false
|
||||
public boolean linkShouldBeKeptAlive() {
|
||||
return false;
|
||||
public ConnectionStarted getConnectionSource() {
|
||||
return connectionSource;
|
||||
}
|
||||
|
||||
public void addPackageReceiver(PackageReceiver pr) {
|
||||
@@ -90,6 +90,5 @@ public abstract class BaseLink {
|
||||
|
||||
//TO OVERRIDE, should be sync
|
||||
public abstract void sendPackage(NetworkPackage np,Device.SendPackageStatusCallback callback);
|
||||
@Deprecated
|
||||
public abstract void sendPackageEncrypted(NetworkPackage np,Device.SendPackageStatusCallback callback, PublicKey key);
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ package org.kde.kdeconnect.Backends;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class BaseLinkProvider {
|
||||
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Backends;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
/**
|
||||
* This class separates the pairing interface for each type of link.
|
||||
* Since different links can pair via different methods, like for LanLink certificate and public key should be shared,
|
||||
* for Bluetooth link they should be paired via bluetooth etc.
|
||||
* Each "Device" instance maintains a hash map for these pairing handlers so that there can be single pairing handler per
|
||||
* per link type per device.
|
||||
* Pairing handler keeps information about device, latest link, and pair status of the link
|
||||
* During first pairing process, the pairing process is nearly same as old process.
|
||||
* After that if any one of the link is paired, then we can say that device is paired, so new link will pair automatically
|
||||
*/
|
||||
|
||||
public abstract class BasePairingHandler {
|
||||
|
||||
protected enum PairStatus{
|
||||
NotPaired,
|
||||
Requested,
|
||||
RequestedByPeer,
|
||||
Paired
|
||||
}
|
||||
|
||||
public interface PairingHandlerCallback {
|
||||
void incomingRequest();
|
||||
void pairingDone();
|
||||
void pairingFailed(String error);
|
||||
void unpaired();
|
||||
}
|
||||
|
||||
|
||||
protected Device mDevice;
|
||||
protected BaseLink mBaseLink;
|
||||
protected PairStatus mPairStatus;
|
||||
protected PairingHandlerCallback mCallback;
|
||||
protected Timer mPairingTimer;
|
||||
|
||||
public BasePairingHandler(Device device, PairingHandlerCallback callback) {
|
||||
this.mDevice = device;
|
||||
this.mCallback = callback;
|
||||
}
|
||||
|
||||
public void setLink(BaseLink baseLink) {
|
||||
this.mBaseLink = baseLink;
|
||||
}
|
||||
|
||||
public boolean isPaired() {
|
||||
return mPairStatus == PairStatus.Paired;
|
||||
}
|
||||
|
||||
public boolean isPairRequested() {
|
||||
return mPairStatus == PairStatus.Requested;
|
||||
}
|
||||
|
||||
public boolean isPairRequestedByPeer() {
|
||||
return mPairStatus == PairStatus.RequestedByPeer;
|
||||
}
|
||||
|
||||
/* To be implemented by respective pairing handler */
|
||||
public abstract NetworkPackage createPairPackage();
|
||||
public abstract void packageReceived(NetworkPackage np) throws Exception;
|
||||
public abstract void requestPairing();
|
||||
public abstract void acceptPairing();
|
||||
public abstract void rejectPairing();
|
||||
public abstract void pairingDone();
|
||||
public abstract void unpair();
|
||||
|
||||
}
|
@@ -20,18 +20,14 @@
|
||||
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.mina.core.future.WriteFuture;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
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.NetworkPackage;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -43,82 +39,32 @@ import java.net.Socket;
|
||||
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;
|
||||
|
||||
public class LanLink extends BaseLink {
|
||||
|
||||
public enum ConnectionStarted {
|
||||
Locally, Remotely;
|
||||
};
|
||||
|
||||
protected 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 IoSession session = null;
|
||||
|
||||
@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;
|
||||
super.disconnect();
|
||||
}
|
||||
|
||||
public void closeSocket() {
|
||||
if (channel == null) {
|
||||
if (session == null) {
|
||||
Log.e("KDE/LanLink", "Not yet connected");
|
||||
return;
|
||||
}
|
||||
channel.close();
|
||||
session.close(true);
|
||||
}
|
||||
|
||||
public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, Channel channel, ConnectionStarted connectionSource, boolean onSsl) {
|
||||
super(context, deviceId, linkProvider);
|
||||
reset(channel, connectionSource, onSsl);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LanLink";
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
|
||||
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
|
||||
}
|
||||
});
|
||||
public LanLink(IoSession session, String deviceId, BaseLinkProvider linkProvider, ConnectionStarted connectionSource) {
|
||||
super(deviceId, linkProvider, connectionSource);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
//Blocking, do not call from main thread
|
||||
private void sendPackageInternal(NetworkPackage np, final Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||
if (channel == null) {
|
||||
if (session == null) {
|
||||
Log.e("KDE/sendPackage", "Not yet connected");
|
||||
callback.sendFailure(new NotYetConnectedException());
|
||||
return;
|
||||
@@ -129,7 +75,7 @@ public class LanLink extends BaseLink {
|
||||
//Prepare socket for the payload
|
||||
final ServerSocket server;
|
||||
if (np.hasPayload()) {
|
||||
server = openTcpSocketOnFreePort(context, getDeviceId(), onSsl);
|
||||
server = openTcpSocketOnFreePort();
|
||||
JSONObject payloadTransferInfo = new JSONObject();
|
||||
payloadTransferInfo.put("port", server.getLocalPort());
|
||||
np.setPayloadTransferInfo(payloadTransferInfo);
|
||||
@@ -139,18 +85,18 @@ public class LanLink extends BaseLink {
|
||||
|
||||
//Encrypt if key provided
|
||||
if (key != null) {
|
||||
np = RsaHelper.encrypt(np, key);
|
||||
np = np.encrypt(key);
|
||||
}
|
||||
|
||||
//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());
|
||||
WriteFuture future = session.write(np.serialize());
|
||||
future.awaitUninterruptibly();
|
||||
if (!future.isWritten()) {
|
||||
//Log.e("KDE/sendPackage", "!future.isWritten()");
|
||||
callback.sendFailure(future.getException());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Send payload
|
||||
if (server != null) {
|
||||
OutputStream socket = null;
|
||||
@@ -218,11 +164,12 @@ public class LanLink extends BaseLink {
|
||||
public void injectNetworkPackage(NetworkPackage np) {
|
||||
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
|
||||
|
||||
try {
|
||||
np = RsaHelper.decrypt(np, privateKey);
|
||||
np = np.decrypt(privateKey);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/onPackageReceived","Exception decrypting the package");
|
||||
Log.e("KDE/onPackageReceived","Exception reading the key needed to decrypt the package");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -231,16 +178,9 @@ public class LanLink extends BaseLink {
|
||||
|
||||
Socket socket = null;
|
||||
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();
|
||||
}
|
||||
|
||||
socket = new Socket();
|
||||
int tcpPort = np.getPayloadTransferInfo().getInt("port");
|
||||
InetSocketAddress address = (InetSocketAddress)channel.remoteAddress();
|
||||
InetSocketAddress address = (InetSocketAddress)session.getRemoteAddress();
|
||||
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
np.setPayload(socket.getInputStream(), np.getPayloadSize());
|
||||
} catch (Exception e) {
|
||||
@@ -254,16 +194,8 @@ 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 {
|
||||
static ServerSocket openTcpSocketOnFreePort() throws IOException {
|
||||
boolean success = false;
|
||||
int tcpPort = 1739;
|
||||
ServerSocket candidateServer = null;
|
||||
@@ -285,48 +217,4 @@ public class LanLink extends BaseLink {
|
||||
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() {
|
||||
|
||||
//We keep the remotely initiated connections, since the remotes require them if they want to request
|
||||
//pairing to us, or connections that are already paired. TODO: Keep connections in the process of pairing
|
||||
|
||||
if (connectionSource == ConnectionStarted.Remotely) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
if (preferences.contains(getDeviceId())) {
|
||||
return true; //Already paired
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -21,97 +21,66 @@
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.future.IoFuture;
|
||||
import org.apache.mina.core.future.IoFutureListener;
|
||||
import org.apache.mina.core.service.IoHandler;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolCodecFilter;
|
||||
import org.apache.mina.filter.codec.textline.LineDelimiter;
|
||||
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
|
||||
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.cert.Certificate;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.Charset;
|
||||
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 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;
|
||||
import java.util.Set;
|
||||
|
||||
public class LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
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;
|
||||
|
||||
private final Context context;
|
||||
private final HashMap<String, LanLink> visibleComputers = new HashMap<>();
|
||||
private final LongSparseArray<LanLink> nioSessions = new LongSparseArray<>();
|
||||
private final LongSparseArray<NioSocketConnector> nioConnectors = new LongSparseArray<>();
|
||||
|
||||
private final HashMap<String, LanLink> visibleComputers = new HashMap<>(); //Links by device id
|
||||
private final LongSparseArray<LanLink> nioLinks = new LongSparseArray<>(); //Links by channel id
|
||||
private NioSocketAcceptor tcpAcceptor = null;
|
||||
private NioDatagramAcceptor udpAcceptor = null;
|
||||
|
||||
private EventLoopGroup bossGroup, workerGroup, udpGroup, clientGroup;
|
||||
private TcpHandler tcpHandler = new TcpHandler();
|
||||
private UdpHandler udpHandler = new UdpHandler();
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private class TcpHandler extends SimpleChannelInboundHandler<String>{
|
||||
private final IoHandler tcpHandler = new IoHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
// Close channel for any sudden exception
|
||||
ctx.channel().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// Called after a long time if remote device closes session unexpectedly, like wifi off
|
||||
public void sessionClosed(IoSession session) throws Exception {
|
||||
try {
|
||||
Channel channel = ctx.channel();
|
||||
final LanLink brokenLink = nioLinks.get(channel.hashCode());
|
||||
long id = session.getId();
|
||||
final LanLink brokenLink = nioSessions.get(id);
|
||||
NioSocketConnector connector = nioConnectors.get(id);
|
||||
if (connector != null) {
|
||||
connector.dispose();
|
||||
nioConnectors.remove(id);
|
||||
}
|
||||
if (brokenLink != null) {
|
||||
nioLinks.remove(channel.hashCode());
|
||||
//Log.i("KDE/LanLinkProvider", "nioLinks.size(): " + nioLinks.size() + " (-)");
|
||||
nioSessions.remove(id);
|
||||
//Log.i("KDE/LanLinkProvider", "nioSessions.size(): " + nioSessions.size() + " (-)");
|
||||
try {
|
||||
brokenLink.closeSocket();
|
||||
} catch (Exception e) {
|
||||
@@ -126,7 +95,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//Wait a bit before emitting connectionLost, in case the same device re-appears
|
||||
//Wait a bit before emiting connectionLost, in case the same device re-appears
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -137,257 +106,147 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
}).start();
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (Exception e) { //If we don't catch it here, Mina will swallow it :/
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "channelInactive exception");
|
||||
Log.e("KDE/LanLinkProvider", "sessionClosed exception");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, String message) throws Exception {
|
||||
//Log.e("KDE/LanLinkProvider", "Received a TCP packet from " + ctx.channel().remoteAddress() + ":" + message);
|
||||
public void messageReceived(IoSession session, Object message) throws Exception {
|
||||
super.messageReceived(session, message);
|
||||
|
||||
if (message.isEmpty()) {
|
||||
Log.e("KDE/LanLinkProvider", "Empty package received");
|
||||
//Log.e("LanLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString());
|
||||
//Log.e("LanLinkProvider","Received:"+message);
|
||||
|
||||
String theMessage = (String) message;
|
||||
if (theMessage.isEmpty()) {
|
||||
Log.w("KDE/LanLinkProvider","Empty package received");
|
||||
return;
|
||||
}
|
||||
|
||||
final Channel channel = ctx.channel();
|
||||
final NetworkPackage np = NetworkPackage.unserialize(message);
|
||||
NetworkPackage np = NetworkPackage.unserialize(theMessage);
|
||||
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Identity package received from a stablished TCP connection from " + np.getString("deviceName"));
|
||||
|
||||
final LanLink.ConnectionStarted connectionStarted = LanLink.ConnectionStarted.Locally;
|
||||
|
||||
// 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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Log.i("KDE/LanLinkProvider", "Identity package received from " + np.getString("deviceName"));
|
||||
|
||||
LanLink link = new LanLink(session, np.getString("deviceId"), LanLinkProvider.this, BaseLink.ConnectionStarted.Locally);
|
||||
nioSessions.put(session.getId(),link);
|
||||
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size());
|
||||
addLink(np, link);
|
||||
} 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());
|
||||
LanLink prevLink = nioSessions.get(session.getId());
|
||||
if (prevLink == null) {
|
||||
Log.e("KDE/LanLinkProvider","Expecting an identity package (A)");
|
||||
} else {
|
||||
link.injectNetworkPackage(np);
|
||||
prevLink.injectNetworkPackage(np);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
};
|
||||
|
||||
private final IoHandler udpHandler = new IoHandlerAdapter() {
|
||||
@Override
|
||||
protected void channelRead0(final ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
|
||||
try {
|
||||
String theMessage = packet.content().toString(CharsetUtil.UTF_8);
|
||||
public void messageReceived(IoSession udpSession, Object message) throws Exception {
|
||||
super.messageReceived(udpSession, message);
|
||||
|
||||
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
|
||||
|
||||
try {
|
||||
//We should receive a string thanks to the TextLineCodecFactory filter
|
||||
String theMessage = (String) message;
|
||||
final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage);
|
||||
final String deviceId = identityPackage.getString("deviceId");
|
||||
|
||||
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity package");
|
||||
Log.e("KDE/LanLinkProvider", "Expecting an identity package (B)");
|
||||
return;
|
||||
} else {
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
if (deviceId.equals(myId)) {
|
||||
Log.i("KDE/LanLinkProvider", "Ignoring my own broadcast");
|
||||
if (identityPackage.getString("deviceId").equals(myId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Log.i("KDE/LanLinkProvider", "Identity package received, creating link");
|
||||
|
||||
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 {
|
||||
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
|
||||
|
||||
final Channel channel = channelFuture.channel();
|
||||
final NioSocketConnector connector = new NioSocketConnector();
|
||||
connector.setHandler(tcpHandler);
|
||||
connector.getSessionConfig().setKeepAlive(true);
|
||||
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
|
||||
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX);
|
||||
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data
|
||||
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory));
|
||||
|
||||
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() {
|
||||
int tcpPort = identityPackage.getInt("tcpPort", port);
|
||||
final ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
future.addListener(new IoFutureListener<IoFuture>() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(IoFuture ioFuture) {
|
||||
try {
|
||||
future.removeListener(this);
|
||||
final IoSession session = ioFuture.getSession();
|
||||
Log.i("KDE/LanLinkProvider", "Connection successful: " + session.isConnected());
|
||||
|
||||
final LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this, BaseLink.ConnectionStarted.Remotely);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
|
||||
link.sendPackage(np2,new Device.SendPackageStatusCallback() {
|
||||
@Override
|
||||
public void run() {
|
||||
reverseConnectionBlackList.remove(deviceId);
|
||||
protected void onSuccess() {
|
||||
nioSessions.put(session.getId(), link);
|
||||
nioConnectors.put(session.getId(), connector);
|
||||
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size());
|
||||
addLink(identityPackage, link);
|
||||
}
|
||||
}, 5*1000);
|
||||
|
||||
// Try to cause a reverse connection
|
||||
onNetworkChange();
|
||||
@Override
|
||||
protected void onFailure(Throwable e) {
|
||||
Log.e("KDE/LanLinkProvider", "Connection failed: could not send identity package back");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}).start();
|
||||
} catch (Exception e) { //If we don't catch it here, Mina will swallow it :/
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "sessionClosed exception");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Exception receiving udp package!!");
|
||||
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(NetworkPackage identityPackage, Channel channel, LanLink.ConnectionStarted connectionOrigin, boolean useSsl) {
|
||||
private void addLink(NetworkPackage identityPackage, LanLink link) {
|
||||
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());
|
||||
} else {
|
||||
//Let's create the link
|
||||
LanLink link = new LanLink(context, deviceId, this, channel, connectionOrigin, useSsl);
|
||||
nioLinks.put(channel.hashCode(), link);
|
||||
visibleComputers.put(deviceId, link);
|
||||
connectionAccepted(identityPackage, link);
|
||||
LanLink oldLink = visibleComputers.get(deviceId);
|
||||
if (oldLink == link) {
|
||||
Log.e("KDE/LanLinkProvider", "oldLink == link. This should not happen!");
|
||||
return;
|
||||
}
|
||||
visibleComputers.put(deviceId, link);
|
||||
connectionAccepted(identityPackage, link);
|
||||
if (oldLink != null) {
|
||||
Log.i("KDE/LanLinkProvider","Removing old connection to same device");
|
||||
oldLink.closeSocket();
|
||||
connectionLost(oldLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,71 +254,64 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
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();
|
||||
}
|
||||
//This handles the case when I'm the new device in the network and somebody answers my introduction package
|
||||
tcpAcceptor = new NioSocketAcceptor();
|
||||
tcpAcceptor.setHandler(tcpHandler);
|
||||
tcpAcceptor.getSessionConfig().setKeepAlive(true);
|
||||
tcpAcceptor.getSessionConfig().setReuseAddress(true);
|
||||
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
|
||||
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX);
|
||||
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data
|
||||
tcpAcceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory));
|
||||
|
||||
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();
|
||||
}
|
||||
udpAcceptor = new NioDatagramAcceptor();
|
||||
udpAcceptor.getSessionConfig().setReuseAddress(true); //Share port if existing
|
||||
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
|
||||
//This one will have the default MaxLineLength of 1KB
|
||||
udpAcceptor.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(
|
||||
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "onStart");
|
||||
//This handles the case when I'm the existing device in the network and receive a "hello" UDP package
|
||||
|
||||
Set<SocketAddress> addresses = udpAcceptor.getLocalAddresses();
|
||||
for (SocketAddress address : addresses) {
|
||||
Log.i("KDE/LanLinkProvider", "UDP unbind old address");
|
||||
udpAcceptor.unbind(address);
|
||||
}
|
||||
|
||||
//Log.i("KDE/LanLinkProvider", "UDP Bind.");
|
||||
udpAcceptor.setHandler(udpHandler);
|
||||
|
||||
try {
|
||||
udpAcceptor.bind(new InetSocketAddress(port));
|
||||
} catch(Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Error: Could not bind udp socket");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
int tcpPort = port;
|
||||
while(!success) {
|
||||
try {
|
||||
tcpAcceptor.bind(new InetSocketAddress(tcpPort));
|
||||
success = true;
|
||||
} catch(Exception e) {
|
||||
tcpPort++;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider","Using tcpPort "+tcpPort);
|
||||
|
||||
//I'm on a new network, let's be polite and introduce myself
|
||||
final int finalTcpPort = tcpPort;
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -473,7 +325,7 @@ 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", finalTcpPort);
|
||||
DatagramSocket socket = null;
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
@@ -491,7 +343,7 @@ 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);
|
||||
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, client, port);
|
||||
socket.send(packet);
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+packet.getAddress());
|
||||
} catch (Exception e) {
|
||||
@@ -501,9 +353,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
socket.close();
|
||||
|
||||
}
|
||||
}).start();
|
||||
@@ -511,22 +361,26 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
@Override
|
||||
public void onNetworkChange() {
|
||||
//Log.e("KDE/LanLinkProvider","onNetworkChange");
|
||||
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
|
||||
//Keep existing connections open while unbinding the socket
|
||||
tcpAcceptor.setCloseOnDeactivation(false);
|
||||
onStop();
|
||||
tcpAcceptor.setCloseOnDeactivation(true);
|
||||
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
|
||||
onStart();
|
||||
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
//Log.i("KDE/LanLinkProvider", "onStop");
|
||||
try {
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
udpGroup.shutdownGracefully();
|
||||
clientGroup.shutdownGracefully();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
udpAcceptor.unbind();
|
||||
tcpAcceptor.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class LanPairingHandler extends BasePairingHandler {
|
||||
|
||||
public LanPairingHandler(Device device, final PairingHandlerCallback callback) {
|
||||
super(device, callback);
|
||||
|
||||
if (device.isPaired()) {
|
||||
mPairStatus = PairStatus.Paired;
|
||||
} else {
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkPackage createPairPackage() {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", true);
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(mDevice.getContext());
|
||||
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
|
||||
np.set("publicKey", publicKey);
|
||||
return np;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packageReceived(NetworkPackage np) throws Exception{
|
||||
|
||||
boolean wantsPair = np.getBoolean("pair");
|
||||
|
||||
if (wantsPair == isPaired()) {
|
||||
if (mPairStatus == PairStatus.Requested) {
|
||||
//Log.e("Device","Unpairing (pair rejected)");
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
hidePairingNotification();
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (wantsPair) {
|
||||
|
||||
//Retrieve their public key
|
||||
try {
|
||||
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", "");
|
||||
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
|
||||
mDevice.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
} catch (Exception e) {
|
||||
//IGNORE
|
||||
}
|
||||
|
||||
if (mPairStatus == PairStatus.Requested) { //We started pairing
|
||||
|
||||
hidePairingNotification();
|
||||
|
||||
pairingDone();
|
||||
|
||||
} else {
|
||||
|
||||
// If device is already paired, accept pairing silently
|
||||
if (mDevice.isPaired()) {
|
||||
acceptPairing();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pairing notifications are still managed by device as there is no other way to
|
||||
// know about notificationId to cancel notification when PairActivity is started
|
||||
// Even putting notificationId in intent does not work because PairActivity can be
|
||||
// started from MainActivity too, so then notificationId cannot be set
|
||||
hidePairingNotification();
|
||||
mDevice.displayPairingNotification();
|
||||
|
||||
mPairingTimer = new Timer();
|
||||
|
||||
mPairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w("KDE/Device","Unpairing (timeout B)");
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
hidePairingNotification();
|
||||
}
|
||||
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
|
||||
mPairStatus = PairStatus.RequestedByPeer;
|
||||
mCallback.incomingRequest();
|
||||
|
||||
}
|
||||
} else {
|
||||
Log.i("KDE/Pairing", "Unpair request");
|
||||
|
||||
if (mPairStatus == PairStatus.Requested) {
|
||||
hidePairingNotification();
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
|
||||
} else if (mPairStatus == PairStatus.Paired) {
|
||||
mCallback.unpaired();
|
||||
}
|
||||
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPairing() {
|
||||
|
||||
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
|
||||
@Override
|
||||
protected void onSuccess() {
|
||||
hidePairingNotification(); //Will stop the pairingTimer if it was running
|
||||
mPairingTimer = new Timer();
|
||||
mPairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
|
||||
Log.w("KDE/Device","Unpairing (timeout A)");
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
}, 30*1000); //Time to wait for the other to accept
|
||||
mPairStatus = PairStatus.Requested;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailure(Throwable e) {
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package));
|
||||
}
|
||||
};
|
||||
mDevice.sendPackage(createPairPackage(), statusCallback);
|
||||
}
|
||||
|
||||
public void hidePairingNotification() {
|
||||
mDevice.hidePairingNotification();
|
||||
if (mPairingTimer != null) {
|
||||
mPairingTimer .cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptPairing() {
|
||||
hidePairingNotification();
|
||||
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
|
||||
@Override
|
||||
protected void onSuccess() {
|
||||
pairingDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailure(Throwable e) {
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
|
||||
}
|
||||
};
|
||||
mDevice.sendPackage(createPairPackage(), statusCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rejectPairing() {
|
||||
hidePairingNotification();
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
mDevice.sendPackage(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pairingDone() {
|
||||
// Store device information needed to create a Device object in a future
|
||||
//Log.e("KDE/PairingDone", "Pairing Done");
|
||||
SharedPreferences.Editor editor = mDevice.getContext().getSharedPreferences(mDevice.getDeviceId(), Context.MODE_PRIVATE).edit();
|
||||
|
||||
if (mDevice.publicKey != null) {
|
||||
try {
|
||||
String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/PairingDone", "Error encoding public key");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String encodedCertificate = Base64.encodeToString(mDevice.certificate.getEncoded(), 0);
|
||||
editor.putString("certificate", encodedCertificate);
|
||||
} catch (NullPointerException n) {
|
||||
Log.w("KDE/PairingDone", "Certificate is null, remote device does not support ssl");
|
||||
} catch (CertificateEncodingException c) {
|
||||
Log.e("KDE/PairingDOne", "Error encoding certificate");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Pairng", "Exception");
|
||||
}
|
||||
editor.apply();
|
||||
|
||||
mPairStatus = PairStatus.Paired;
|
||||
mCallback.pairingDone();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpair() {
|
||||
mPairStatus = PairStatus.NotPaired;
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
mDevice.sendPackage(np);
|
||||
}
|
||||
}
|
@@ -20,11 +20,8 @@
|
||||
|
||||
package org.kde.kdeconnect.Backends.LoopbackBackend;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
@@ -32,33 +29,35 @@ import java.security.PublicKey;
|
||||
|
||||
public class LoopbackLink extends BaseLink {
|
||||
|
||||
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
|
||||
super(context, "loopback", linkProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LoopbackLink";
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
|
||||
return new LoopbackPairingHandler(device, callback);
|
||||
public LoopbackLink(BaseLinkProvider linkProvider) {
|
||||
super("loopback", linkProvider, ConnectionStarted.Remotely);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPackage(NetworkPackage in, Device.SendPackageStatusCallback callback) {
|
||||
packageReceived(in);
|
||||
if (in.hasPayload()) {
|
||||
callback.sendProgress(0);
|
||||
in.setPayload(in.getPayload(), in.getPayloadSize());
|
||||
callback.sendProgress(100);
|
||||
}
|
||||
callback.sendSuccess();
|
||||
sendPackageEncrypted(in, callback, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||
sendPackage(np, callback);
|
||||
public void sendPackageEncrypted(NetworkPackage in, Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||
try {
|
||||
if (key != null) {
|
||||
in = in.encrypt(key);
|
||||
}
|
||||
String s = in.serialize();
|
||||
NetworkPackage out= NetworkPackage.unserialize(s);
|
||||
if (key != null) {
|
||||
out = out.decrypt(privateKey);
|
||||
}
|
||||
packageReceived(out);
|
||||
if (in.hasPayload()) {
|
||||
callback.sendProgress(0);
|
||||
out.setPayload(in.getPayload(), in.getPayloadSize());
|
||||
callback.sendProgress(100);
|
||||
}
|
||||
callback.sendSuccess();
|
||||
} catch(Exception e) {
|
||||
callback.sendFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
|
||||
@Override
|
||||
public void onNetworkChange() {
|
||||
NetworkPackage np = NetworkPackage.createIdentityPackage(context);
|
||||
connectionAccepted(np, new LoopbackLink(context, this));
|
||||
connectionAccepted(np, new LoopbackLink(this));
|
||||
}
|
||||
/*
|
||||
@Override
|
||||
|
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Backends.LoopbackBackend;
|
||||
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
|
||||
public class LoopbackPairingHandler extends LanPairingHandler{
|
||||
|
||||
public LoopbackPairingHandler(Device device, PairingHandlerCallback callback) {
|
||||
super(device, callback);
|
||||
}
|
||||
// Extending from LanPairingHandler, as it is similar to it
|
||||
}
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -28,15 +29,18 @@ import android.content.SharedPreferences;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -152,17 +156,13 @@ public class BackgroundService extends Service {
|
||||
|
||||
}
|
||||
|
||||
public ArrayList<BaseLinkProvider> getLinkProviders() {
|
||||
return linkProviders;
|
||||
}
|
||||
|
||||
public Device getDevice(String id) {
|
||||
return devices.get(id);
|
||||
}
|
||||
|
||||
private void cleanDevices() {
|
||||
for(Device d : devices.values()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByOtherEnd() && d.getConnectionSource() == BaseLink.ConnectionStarted.Remotely) {
|
||||
d.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -182,8 +182,8 @@ public class BackgroundService extends Service {
|
||||
} else {
|
||||
Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId);
|
||||
device = new Device(BackgroundService.this, identityPackage, link);
|
||||
if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer()
|
||||
|| link.linkShouldBeKeptAlive()
|
||||
if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByOtherEnd()
|
||||
|| link.getConnectionSource() == BaseLink.ConnectionStarted.Locally
|
||||
||!discoveryModeAcquisitions.isEmpty() )
|
||||
{
|
||||
devices.put(deviceId, device);
|
||||
@@ -254,7 +254,7 @@ public class BackgroundService extends Service {
|
||||
|
||||
Log.i("KDE/BackgroundService", "Service not started yet, initializing...");
|
||||
|
||||
initializeSecurityParameters();
|
||||
initializeRsaKeys();
|
||||
loadRememberedDevicesFromSettings();
|
||||
registerLinkProviders();
|
||||
|
||||
@@ -264,11 +264,62 @@ public class BackgroundService extends Service {
|
||||
for (BaseLinkProvider a : linkProviders) {
|
||||
a.onStart();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void initializeSecurityParameters() {
|
||||
RsaHelper.initialiseRsaKeys(this);
|
||||
SslHelper.initialiseCertificate(this);
|
||||
private void initializeRsaKeys() {
|
||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
if (!settings.contains("publicKey") || !settings.contains("privateKey")) {
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
keyPair = keyGen.genKeyPair();
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initializeRsaKeys","Exception");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||
|
||||
SharedPreferences.Editor edit = settings.edit();
|
||||
edit.putString("publicKey",Base64.encodeToString(publicKey, 0).trim()+"\n");
|
||||
edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
|
||||
edit.apply();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// Encryption and decryption test
|
||||
//================================
|
||||
|
||||
try {
|
||||
|
||||
NetworkPackage np = NetworkPackage.createIdentityPackage(this);
|
||||
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
byte[] publicKeyBytes = Base64.decode(globalSettings.getString("publicKey",""), 0);
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
|
||||
np.encrypt(publicKey);
|
||||
|
||||
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0);
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
|
||||
NetworkPackage decrypted = np.decrypt(privateKey);
|
||||
Log.e("ENCRYPTION AND DECRYPTION TEST", decrypted.serialize());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("ENCRYPTION AND DECRYPTION TEST","Exception: "+e);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,14 +350,11 @@ public class BackgroundService extends Service {
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
//This will be called for each intent launch, even if the service is already started and it is reused
|
||||
mutex.lock();
|
||||
try {
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
||||
}
|
||||
callbacks.clear();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
||||
}
|
||||
callbacks.clear();
|
||||
mutex.unlock();
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
@@ -320,11 +368,8 @@ public class BackgroundService extends Service {
|
||||
public void run() {
|
||||
if (callback != null) {
|
||||
mutex.lock();
|
||||
try {
|
||||
callbacks.add(callback);
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
callbacks.add(callback);
|
||||
mutex.unlock();
|
||||
}
|
||||
Intent serviceIntent = new Intent(c, BackgroundService.class);
|
||||
c.startService(serviceIntent);
|
||||
|
@@ -28,6 +28,8 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@@ -35,27 +37,22 @@ import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Helpers.ObjectsHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -66,37 +63,21 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
private final String deviceId;
|
||||
private String name;
|
||||
public PublicKey publicKey;
|
||||
public Certificate certificate;
|
||||
private int notificationId;
|
||||
private int protocolVersion;
|
||||
|
||||
private DeviceType deviceType;
|
||||
private PairStatus pairStatus;
|
||||
|
||||
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
|
||||
private Map<String, BasePairingHandler> pairingHandlers = new HashMap<String, BasePairingHandler>();
|
||||
private Timer pairingTimer;
|
||||
|
||||
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
||||
|
||||
private ArrayList<String> incomingCapabilities = new ArrayList<>();
|
||||
private ArrayList<String> outgoingCapabilities = new ArrayList<>();
|
||||
|
||||
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Plugin> failedPlugins = new ConcurrentHashMap<>();
|
||||
|
||||
private ArrayList<String> unsupportedPlugins = new ArrayList<>();
|
||||
private HashSet<String> supportedIncomingInterfaces = new HashSet<>();
|
||||
private HashSet<String> supportedOutgoingInterfaces = new HashSet<>();
|
||||
|
||||
private HashMap<String, ArrayList<String>> pluginsByIncomingInterface;
|
||||
private HashMap<String, ArrayList<String>> pluginsByOutgoingInterface;
|
||||
|
||||
private final SharedPreferences settings;
|
||||
|
||||
public ArrayList<String> getUnsupportedPlugins() {
|
||||
return unsupportedPlugins;
|
||||
}
|
||||
|
||||
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
public interface PluginsChangedListener {
|
||||
@@ -105,6 +86,8 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
public enum PairStatus {
|
||||
NotPaired,
|
||||
Requested,
|
||||
RequestedByPeer,
|
||||
Paired
|
||||
}
|
||||
|
||||
@@ -148,14 +131,12 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
|
||||
|
||||
try {
|
||||
String publicKeyStr = settings.getString("publicKey", null);
|
||||
if (publicKeyStr != null) {
|
||||
byte[] publicKeyBytes = Base64.decode(publicKeyStr, 0);
|
||||
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
}
|
||||
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
|
||||
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device","Exception deserializing stored public key for device");
|
||||
unpair();
|
||||
Log.e("KDE/Device","Exception");
|
||||
}
|
||||
|
||||
reloadPluginsFromSettings();
|
||||
@@ -202,10 +183,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
|
||||
public int compareProtocolVersion() {
|
||||
return protocolVersion - NetworkPackage.ProtocolVersion;
|
||||
@@ -214,6 +191,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Pairing-related functions
|
||||
//
|
||||
@@ -222,28 +200,17 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return pairStatus == PairStatus.Paired;
|
||||
}
|
||||
|
||||
/* Asks all pairing handlers that, is pair requested? */
|
||||
public boolean isPairRequested() {
|
||||
boolean pairRequested = false;
|
||||
for (BasePairingHandler ph: pairingHandlers.values()) {
|
||||
pairRequested = pairRequested || ph.isPairRequested();
|
||||
}
|
||||
return pairRequested;
|
||||
return pairStatus == PairStatus.Requested;
|
||||
}
|
||||
|
||||
/* Asks all pairing handlers that, is pair requested by peer? */
|
||||
public boolean isPairRequestedByPeer() {
|
||||
boolean pairRequestedByPeer = false;
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
pairRequestedByPeer = pairRequestedByPeer || ph.isPairRequestedByPeer();
|
||||
}
|
||||
return pairRequestedByPeer;
|
||||
public boolean isPairRequestedByOtherEnd() {
|
||||
return pairStatus == PairStatus.RequestedByPeer;
|
||||
}
|
||||
|
||||
public void addPairingCallback(PairingCallback callback) {
|
||||
pairingCallback.add(callback);
|
||||
}
|
||||
|
||||
public void removePairingCallback(PairingCallback callback) {
|
||||
pairingCallback.remove(callback);
|
||||
}
|
||||
@@ -258,6 +225,15 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
cb.pairingFailed(res.getString(R.string.error_already_paired));
|
||||
}
|
||||
return;
|
||||
case Requested:
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(res.getString(R.string.error_already_requested));
|
||||
}
|
||||
return;
|
||||
case RequestedByPeer:
|
||||
Log.d("requestPairing", "Pairing already started by the other end, accepting their request.");
|
||||
acceptPairing();
|
||||
return;
|
||||
case NotPaired:
|
||||
;
|
||||
}
|
||||
@@ -269,35 +245,59 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
ph.requestPairing();
|
||||
}
|
||||
//Send our own public key
|
||||
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
|
||||
sendPackage(np, new SendPackageStatusCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
hidePairingNotification(); //Will stop the pairingTimer if it was running
|
||||
pairingTimer = new Timer();
|
||||
pairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_timed_out));
|
||||
}
|
||||
Log.e("KDE/Device", "Unpairing (timeout A)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
}, 30 * 1000); //Time to wait for the other to accept
|
||||
pairStatus = PairStatus.Requested;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
|
||||
}
|
||||
Log.e("KDE/Device", "Unpairing (sendFailed A)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void hidePairingNotification() {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationId);
|
||||
if (pairingTimer != null) {
|
||||
pairingTimer.cancel();
|
||||
}
|
||||
BackgroundService.removeGuiInUseCounter(context);
|
||||
}
|
||||
|
||||
public void unpair() {
|
||||
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
ph.unpair();
|
||||
}
|
||||
unpairInternal(); // Even if there are no pairing handlers, unpair
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does not send an unpair package, instead it unpairs internally by deleting trusted device info. . Likely to be called after sending package from
|
||||
* pairing handler
|
||||
*/
|
||||
private void unpairInternal() {
|
||||
|
||||
//Log.e("Device","Unpairing (unpairInternal)");
|
||||
//Log.e("Device","Unpairing (unpair)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
preferences.edit().remove(deviceId).apply();
|
||||
|
||||
// FIXME : We delete all device info here, but the xml file still persists
|
||||
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
devicePreferences.edit().clear().apply();
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
sendPackage(np);
|
||||
|
||||
for (PairingCallback cb : pairingCallback) cb.unpaired();
|
||||
|
||||
@@ -305,7 +305,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
}
|
||||
|
||||
/* This method should be called after pairing is done from pairing handler. Calling this method again should not create any problem as most of the things will get over writter*/
|
||||
private void pairingDone() {
|
||||
|
||||
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
|
||||
@@ -318,9 +317,12 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
preferences.edit().putBoolean(deviceId,true).apply();
|
||||
|
||||
SharedPreferences.Editor editor = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE).edit();
|
||||
editor.putString("deviceName", name);
|
||||
//Store device information needed to create a Device object in a future
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.putString("deviceName", getName());
|
||||
editor.putString("deviceType", deviceType.toString());
|
||||
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
editor.apply();
|
||||
|
||||
reloadPluginsFromSettings();
|
||||
@@ -331,28 +333,39 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
}
|
||||
|
||||
/* This method is called after accepting pair request form GUI */
|
||||
public void acceptPairing() {
|
||||
|
||||
Log.i("KDE/Device", "Accepted pair request started by the other device");
|
||||
Log.i("KDE/Device","Accepted pair request started by the other device");
|
||||
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
ph.acceptPairing();
|
||||
}
|
||||
//Send our own public key
|
||||
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
|
||||
sendPackage(np, new SendPackageStatusCallback() {
|
||||
@Override
|
||||
protected void onSuccess() {
|
||||
pairingDone();
|
||||
}
|
||||
@Override
|
||||
protected void onFailure(Throwable e) {
|
||||
Log.e("Device","Unpairing (sendFailed B)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_not_reachable));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/* This method is called after rejecting pairing from GUI */
|
||||
public void rejectPairing() {
|
||||
|
||||
Log.i("KDE/Device", "Rejected pair request started by the other device");
|
||||
Log.i("KDE/Device","Rejected pair request started by the other device");
|
||||
|
||||
//Log.e("Device","Unpairing (rejectPairing)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
ph.rejectPairing();
|
||||
}
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
sendPackage(np);
|
||||
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
|
||||
@@ -360,52 +373,8 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Notification related methods used during pairing
|
||||
//
|
||||
public int getNotificationId() {
|
||||
return notificationId;
|
||||
}
|
||||
|
||||
public void displayPairingNotification() {
|
||||
|
||||
hidePairingNotification();
|
||||
|
||||
notificationId = (int)System.currentTimeMillis();
|
||||
|
||||
Intent intent = new Intent(getContext(), MaterialActivity.class);
|
||||
intent.putExtra("deviceId", getDeviceId());
|
||||
intent.putExtra("notificationId", notificationId);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
Resources res = getContext().getResources();
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(getContext())
|
||||
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
|
||||
.setContentText(res.getString(R.string.tap_to_answer))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setTicker(res.getString(R.string.pair_requested))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
try {
|
||||
BackgroundService.addGuiInUseCounter(context);
|
||||
notificationManager.notify(notificationId, noti);
|
||||
} catch(Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
}
|
||||
|
||||
public void hidePairingNotification() {
|
||||
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationId);
|
||||
BackgroundService.removeGuiInUseCounter(context);
|
||||
}
|
||||
|
||||
//
|
||||
// ComputerLink-related functions
|
||||
@@ -431,20 +400,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
this.deviceType = DeviceType.FromString(identityPackage.getString("deviceType", "desktop"));
|
||||
}
|
||||
|
||||
if (identityPackage.has("certificate")) {
|
||||
String certificateString = identityPackage.getString("certificate");
|
||||
|
||||
try {
|
||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||
certificate = SslHelper.parseCertificate(certificateBytes);
|
||||
Log.i("KDE/Device", "Got certificate ");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Error getting certificate");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
links.add(link);
|
||||
|
||||
@@ -460,36 +415,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
Log.i("KDE/Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size());
|
||||
|
||||
if (!pairingHandlers.containsKey(link.getName())) {
|
||||
BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() {
|
||||
@Override
|
||||
public void incomingRequest() {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.incomingRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pairingDone() {
|
||||
Device.this.pairingDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pairingFailed(String error) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpaired() {
|
||||
unpairInternal();
|
||||
}
|
||||
};
|
||||
pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback));
|
||||
}
|
||||
pairingHandlers.get(link.getName()).setLink(link);
|
||||
|
||||
/*
|
||||
Collections.sort(links, new Comparator<BaseLink>() {
|
||||
@Override
|
||||
@@ -509,21 +434,9 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
public void removeLink(BaseLink link) {
|
||||
//FilesHelper.LogOpenFileCount();
|
||||
|
||||
/* Remove pairing handler corresponding to that link too if it was the only link*/
|
||||
boolean linkPresent = false;
|
||||
for (BaseLink bl : links) {
|
||||
if (bl.getName().equals(link.getName())) {
|
||||
linkPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!linkPresent) {
|
||||
pairingHandlers.remove(link.getName());
|
||||
}
|
||||
|
||||
link.removePackageReceiver(this);
|
||||
links.remove(link);
|
||||
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
|
||||
Log.i("KDE/Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size());
|
||||
if (links.isEmpty()) {
|
||||
reloadPluginsFromSettings();
|
||||
}
|
||||
@@ -532,72 +445,139 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
@Override
|
||||
public void onPackageReceived(NetworkPackage np) {
|
||||
|
||||
hackToMakeRetrocompatiblePacketTypes(np);
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) {
|
||||
|
||||
if (NetworkPackage.PACKAGE_TYPE_PAIR.equals(np.getType())) {
|
||||
Log.i("KDE/Device","Pair package");
|
||||
|
||||
Log.i("KDE/Device", "Pair package");
|
||||
boolean wantsPair = np.getBoolean("pair");
|
||||
|
||||
for (BasePairingHandler ph: pairingHandlers.values()) {
|
||||
try {
|
||||
ph.packageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("PairingPackageReceived","Exception");
|
||||
}
|
||||
}
|
||||
} else if (NetworkPackage.PACKAGE_TYPE_CAPABILITIES.equals(np.getType())) {
|
||||
ArrayList<String> newIncomingCapabilities = np.getStringList("IncomingCapabilities");
|
||||
ArrayList<String> newOutgoingCapabilities = np.getStringList("OutgoingCapabilities");
|
||||
if (!ObjectsHelper.equals(newIncomingCapabilities, incomingCapabilities) ||
|
||||
!ObjectsHelper.equals(newOutgoingCapabilities, outgoingCapabilities)) {
|
||||
incomingCapabilities = newIncomingCapabilities;
|
||||
outgoingCapabilities = newOutgoingCapabilities;
|
||||
reloadPluginsFromSettings();
|
||||
}
|
||||
|
||||
} else if (isPaired()) {
|
||||
|
||||
//If capabilities are not supported, iterate all plugins
|
||||
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
|
||||
if (targetPlugins != null && !targetPlugins.isEmpty()) {
|
||||
for (String pluginKey : targetPlugins) {
|
||||
Plugin plugin = plugins.get(pluginKey);
|
||||
try {
|
||||
plugin.onPackageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPackageReceived()");
|
||||
if (wantsPair == isPaired()) {
|
||||
if (pairStatus == PairStatus.Requested) {
|
||||
//Log.e("Device","Unpairing (pair rejected)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
hidePairingNotification();
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
|
||||
return;
|
||||
}
|
||||
|
||||
if (wantsPair) {
|
||||
|
||||
//Retrieve their public key
|
||||
try {
|
||||
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n","");
|
||||
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
|
||||
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device","Pairing exception: Received incorrect key");
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_invalid_key));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pairStatus == PairStatus.Requested) { //We started pairing
|
||||
|
||||
hidePairingNotification();
|
||||
|
||||
pairingDone();
|
||||
|
||||
} else {
|
||||
|
||||
Intent intent = new Intent(context, MaterialActivity.class);
|
||||
intent.putExtra("deviceId", deviceId);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
Resources res = context.getResources();
|
||||
|
||||
hidePairingNotification();
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
|
||||
.setContentText(res.getString(R.string.tap_to_answer))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setTicker(res.getString(R.string.pair_requested))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationId = (int)System.currentTimeMillis();
|
||||
try {
|
||||
BackgroundService.addGuiInUseCounter(context);
|
||||
notificationManager.notify(notificationId, noti);
|
||||
} catch(Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
|
||||
pairingTimer = new Timer();
|
||||
pairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.e("KDE/Device","Unpairing (timeout B)");
|
||||
hidePairingNotification();
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
|
||||
pairStatus = PairStatus.RequestedByPeer;
|
||||
for (PairingCallback cb : pairingCallback) cb.incomingRequest();
|
||||
|
||||
}
|
||||
} else {
|
||||
Log.i("KDE/Pairing","Unpair request");
|
||||
|
||||
if (pairStatus == PairStatus.Requested) {
|
||||
hidePairingNotification();
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
} else if (pairStatus == PairStatus.Paired) {
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
preferences.edit().remove(deviceId).apply();
|
||||
}
|
||||
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
|
||||
reloadPluginsFromSettings();
|
||||
|
||||
for (PairingCallback cb : pairingCallback) cb.unpaired();
|
||||
|
||||
}
|
||||
} else if (isPaired()) {
|
||||
|
||||
for (Plugin plugin : plugins.values()) {
|
||||
try {
|
||||
plugin.onPackageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived()");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
//Log.e("KDE/onPackageReceived","Device not paired, will pass package to unpairedPackageListeners");
|
||||
|
||||
// If it is pair package, it should be captured by "if" at start
|
||||
// If not and device is paired, it should be captured by isPaired
|
||||
// Else unpair, this handles the situation when one device unpairs, but other dont know like unpairing when wi-fi is off
|
||||
|
||||
unpair();
|
||||
|
||||
//If capabilities are not supported, iterate all plugins
|
||||
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
|
||||
if (targetPlugins != null && !targetPlugins.isEmpty()) {
|
||||
for (String pluginKey : targetPlugins) {
|
||||
Plugin plugin = plugins.get(pluginKey);
|
||||
try {
|
||||
plugin.onUnpairedDevicePackageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getDisplayName() + "'s onPackageReceived() in unPairedPackageListeners");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
|
||||
if (pairStatus != PairStatus.Requested) {
|
||||
unpair();
|
||||
}
|
||||
|
||||
for (Plugin plugin : plugins.values()) {
|
||||
try {
|
||||
plugin.onUnpairedDevicePackageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived() in unPairedPackageListeners");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -637,13 +617,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
//Async
|
||||
public void sendPackage(final NetworkPackage np, final SendPackageStatusCallback callback) {
|
||||
|
||||
hackToMakeRetrocompatiblePacketTypes(np);
|
||||
|
||||
if (protocolVersion >= 6 && !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.e("sendPackage", "Sending package...");
|
||||
//Log.e("sendPackage", np.serialize());
|
||||
|
||||
@@ -652,7 +625,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 = (!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) {
|
||||
@@ -699,40 +672,46 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private synchronized boolean addPlugin(final String pluginKey) {
|
||||
private synchronized void addPlugin(final String pluginKey) {
|
||||
Plugin existing = plugins.get(pluginKey);
|
||||
if (existing != null) {
|
||||
//Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
|
||||
return true;
|
||||
Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
|
||||
return;
|
||||
}
|
||||
|
||||
final Plugin plugin = PluginFactory.instantiatePluginForDevice(context, pluginKey, this);
|
||||
if (plugin == null) {
|
||||
Log.e("KDE/addPlugin","could not instantiate plugin: "+pluginKey);
|
||||
failedPlugins.put(pluginKey, null);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean success;
|
||||
try {
|
||||
success = plugin.onCreate();
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/addPlugin", "Exception loading plugin " + pluginKey);
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (success) {
|
||||
//Log.e("addPlugin","added " + pluginKey);
|
||||
failedPlugins.remove(pluginKey);
|
||||
plugins.put(pluginKey, plugin);
|
||||
} else {
|
||||
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
|
||||
plugins.remove(pluginKey);
|
||||
failedPlugins.put(pluginKey, plugin);
|
||||
}
|
||||
boolean success;
|
||||
try {
|
||||
success = plugin.onCreate();
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/addPlugin", "Exception loading plugin " + pluginKey);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
//Log.e("addPlugin","added " + pluginKey);
|
||||
failedPlugins.remove(pluginKey);
|
||||
plugins.put(pluginKey, plugin);
|
||||
} else {
|
||||
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
|
||||
plugins.remove(pluginKey);
|
||||
failedPlugins.put(pluginKey, plugin);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private synchronized boolean removePlugin(String pluginKey) {
|
||||
@@ -756,12 +735,22 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
Log.e("KDE/removePlugin","Exception calling onDestroy for plugin "+pluginKey);
|
||||
}
|
||||
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setPluginEnabled(String pluginKey, boolean value) {
|
||||
settings.edit().putBoolean(pluginKey,value).apply();
|
||||
reloadPluginsFromSettings();
|
||||
if (value && isPaired() && isReachable()) addPlugin(pluginKey);
|
||||
else removePlugin(pluginKey);
|
||||
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(Device.this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isPluginEnabled(String pluginKey) {
|
||||
@@ -780,104 +769,20 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
Set<String> availablePlugins = PluginFactory.getAvailablePlugins();
|
||||
|
||||
ArrayList<String> newUnsupportedPlugins = new ArrayList<>();
|
||||
HashSet<String> newSupportedIncomingInterfaces = new HashSet<>();
|
||||
HashSet<String> newSupportedOutgoingInterfaces = new HashSet<>();
|
||||
HashMap<String, ArrayList<String>> newPluginsByIncomingInterface = new HashMap<>();
|
||||
HashMap<String, ArrayList<String>> newPluginsByOutgoingInterface = new HashMap<>();
|
||||
|
||||
final boolean supportsCapabilities = (protocolVersion >= 6);
|
||||
|
||||
for (String pluginKey : availablePlugins) {
|
||||
|
||||
PluginFactory.PluginInfo pluginInfo = PluginFactory.getPluginInfo(context, pluginKey);
|
||||
Set<String> incomingInterfaces = pluginInfo.getSupportedPackageTypes();
|
||||
Set<String> outgoingInterfaces = pluginInfo.getOutgoingPackageTypes();
|
||||
|
||||
boolean pluginEnabled = false;
|
||||
boolean listenToUnpaired = pluginInfo.listenToUnpaired();
|
||||
for(String pluginKey : availablePlugins) {
|
||||
boolean enabled = false;
|
||||
boolean listenToUnpaired = PluginFactory.getPluginInfo(context, pluginKey).listenToUnpaired();
|
||||
if ((isPaired() || listenToUnpaired) && isReachable()) {
|
||||
pluginEnabled = isPluginEnabled(pluginKey);
|
||||
enabled = isPluginEnabled(pluginKey);
|
||||
}
|
||||
|
||||
//TODO: Check for plugins that will fail to load before checking the capabilities
|
||||
|
||||
if (supportsCapabilities && (!incomingInterfaces.isEmpty() || !outgoingInterfaces.isEmpty())) {
|
||||
HashSet<String> supportedOut = new HashSet<>(outgoingInterfaces);
|
||||
supportedOut.retainAll(incomingCapabilities); //Intersection
|
||||
HashSet<String> supportedIn = new HashSet<>(incomingInterfaces);
|
||||
supportedIn.retainAll(outgoingCapabilities);
|
||||
if (supportedOut.isEmpty() && supportedIn.isEmpty()) {
|
||||
newUnsupportedPlugins.add(pluginKey);
|
||||
if (pluginEnabled) {
|
||||
//We still want to announce this capability, to prevent a deadlock
|
||||
newSupportedOutgoingInterfaces.addAll(outgoingInterfaces);
|
||||
newSupportedIncomingInterfaces.addAll(incomingInterfaces);
|
||||
pluginEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginEnabled) {
|
||||
boolean success = addPlugin(pluginKey);
|
||||
|
||||
if (success) {
|
||||
|
||||
newSupportedIncomingInterfaces.addAll(incomingInterfaces);
|
||||
newSupportedOutgoingInterfaces.addAll(outgoingInterfaces);
|
||||
|
||||
for (String packageType : incomingInterfaces) {
|
||||
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
|
||||
ArrayList<String> plugins = newPluginsByIncomingInterface.get(packageType);
|
||||
if (plugins == null) plugins = new ArrayList<>();
|
||||
plugins.add(pluginKey);
|
||||
newPluginsByIncomingInterface.put(packageType, plugins);
|
||||
}
|
||||
|
||||
for (String packageType : outgoingInterfaces) {
|
||||
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
|
||||
ArrayList<String> plugins = newPluginsByOutgoingInterface.get(packageType);
|
||||
if (plugins == null) plugins = new ArrayList<>();
|
||||
plugins.add(pluginKey);
|
||||
newPluginsByOutgoingInterface.put(packageType, plugins);
|
||||
}
|
||||
|
||||
}
|
||||
if (enabled) {
|
||||
addPlugin(pluginKey);
|
||||
} else {
|
||||
removePlugin(pluginKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean capabilitiesChanged = false;
|
||||
if (!newSupportedIncomingInterfaces.equals(supportedIncomingInterfaces) ||
|
||||
!newSupportedOutgoingInterfaces.equals(pluginsByOutgoingInterface)) {
|
||||
capabilitiesChanged = true;
|
||||
}
|
||||
|
||||
pluginsByOutgoingInterface = newPluginsByOutgoingInterface;
|
||||
pluginsByIncomingInterface = newPluginsByIncomingInterface;
|
||||
supportedIncomingInterfaces = newSupportedIncomingInterfaces;
|
||||
supportedOutgoingInterfaces = newSupportedOutgoingInterfaces;
|
||||
unsupportedPlugins = newUnsupportedPlugins;
|
||||
|
||||
if (!unsupportedPlugins.isEmpty()) {
|
||||
Log.i("ReloadPlugins", "not loading " + Arrays.toString(unsupportedPlugins.toArray()) + " because of unmatched capabilities");
|
||||
}
|
||||
|
||||
onPluginsChanged();
|
||||
|
||||
//Only send capabilities to devices using protocol version 6 or later
|
||||
if (capabilitiesChanged && isReachable() && isPaired() && protocolVersion >= 6) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CAPABILITIES);
|
||||
np.set("IncomingCapabilities", new ArrayList<>(newSupportedIncomingInterfaces));
|
||||
np.set("OutgoingCapabilities", new ArrayList<>(newSupportedOutgoingInterfaces));
|
||||
sendPackage(np);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void onPluginsChanged() {
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(Device.this);
|
||||
}
|
||||
@@ -905,23 +810,13 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deviceShouldBeKeptAlive() {
|
||||
public BaseLink.ConnectionStarted getConnectionSource() {
|
||||
for(BaseLink l : links) {
|
||||
if (l.linkShouldBeKeptAlive()) {
|
||||
return true;
|
||||
if (l.getConnectionSource() == BaseLink.ConnectionStarted.Locally) {
|
||||
return BaseLink.ConnectionStarted.Locally;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void hackToMakeRetrocompatiblePacketTypes(NetworkPackage np) {
|
||||
if (protocolVersion >= 6) return;
|
||||
np.mType = np.getType().replace(".request","");
|
||||
}
|
||||
|
||||
public String hackToMakeRetrocompatiblePacketTypes(String type) {
|
||||
if (protocolVersion >= 6) return type;
|
||||
return type.replace(".request","");
|
||||
return BaseLink.ConnectionStarted.Remotely;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -23,87 +23,44 @@ package org.kde.kdeconnect.Helpers;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ContactsHelper {
|
||||
|
||||
public static Map<String, String> phoneNumberLookup(Context context, String number) {
|
||||
public static String phoneNumberLookup(Context context, String number) {
|
||||
|
||||
//Log.e("PhoneNumberLookup", number);
|
||||
|
||||
Map<String, String> contactInfo = new HashMap<String, String>();
|
||||
|
||||
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(
|
||||
uri,
|
||||
new String[] {
|
||||
PhoneLookup.DISPLAY_NAME,
|
||||
ContactsContract.PhoneLookup.PHOTO_URI
|
||||
PhoneLookup.DISPLAY_NAME
|
||||
/*, PhoneLookup.TYPE
|
||||
, PhoneLookup.LABEL
|
||||
, PhoneLookup.ID */
|
||||
},
|
||||
null, null, null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return contactInfo;
|
||||
return number;
|
||||
}
|
||||
|
||||
// Take the first match only
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int nameIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
contactInfo.put("name", cursor.getString(nameIndex));
|
||||
}
|
||||
|
||||
nameIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
|
||||
if (nameIndex != -1) {
|
||||
contactInfo.put("photoID", cursor.getString(nameIndex));
|
||||
}
|
||||
|
||||
if (!contactInfo.isEmpty()) {
|
||||
String name = cursor.getString(nameIndex);
|
||||
//Log.e("PhoneNumberLookup", "success: " + name);
|
||||
cursor.close();
|
||||
return contactInfo;
|
||||
return name + " (" + number + ")";
|
||||
}
|
||||
}
|
||||
|
||||
return contactInfo;
|
||||
}
|
||||
if (cursor != null) cursor.close();
|
||||
|
||||
public static String photoId64Encoded(Context context, String photoId) {
|
||||
if (photoId == null) {
|
||||
return new String();
|
||||
}
|
||||
Uri photoUri = Uri.parse(photoId);
|
||||
Uri displayPhotoUri = Uri.withAppendedPath(photoUri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
|
||||
return number;
|
||||
|
||||
byte[] buffer = null;
|
||||
Base64OutputStream out = null;
|
||||
ByteArrayOutputStream encodedPhoto = null;
|
||||
try {
|
||||
encodedPhoto = new ByteArrayOutputStream();
|
||||
out = new Base64OutputStream(encodedPhoto, Base64.DEFAULT);
|
||||
InputStream fd2 = context.getContentResolver().openInputStream(photoUri);
|
||||
buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = fd2.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
return encodedPhoto.toString();
|
||||
} catch (Exception ex) {
|
||||
Log.e("ContactsHelper", ex.toString());
|
||||
return new String();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
public class ObjectsHelper {
|
||||
public static boolean equals(Object a, Object b) {
|
||||
return (a == null) ? (b == null) : a.equals(b);
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class RandomHelper {
|
||||
public static SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
private static final char[] symbols = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
|
||||
"abcdefghijklmnopqrstuvwxyz"+
|
||||
"1234567890").toCharArray();
|
||||
|
||||
|
||||
public static String randomString(int length) {
|
||||
char[] buffer= new char[length];
|
||||
for (int idx = 0; idx < length; ++idx) {
|
||||
buffer[idx] = symbols[secureRandom.nextInt(symbols.length)];
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
}
|
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Helpers.SecurityHelpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class RsaHelper {
|
||||
|
||||
public static void initialiseRsaKeys(Context context) {
|
||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
if (!settings.contains("publicKey") || !settings.contains("privateKey")) {
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
keyPair = keyGen.genKeyPair();
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initializeRsaKeys", "Exception");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||
|
||||
SharedPreferences.Editor edit = settings.edit();
|
||||
edit.putString("publicKey", Base64.encodeToString(publicKey, 0).trim()+"\n");
|
||||
edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
|
||||
edit.apply();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static PublicKey getPublicKey (Context context) throws Exception{
|
||||
try {
|
||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
return publicKey;
|
||||
}catch (Exception e){
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static PublicKey getPublicKey(Context context, String deviceId) throws Exception{
|
||||
try {
|
||||
SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
return publicKey;
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static PrivateKey getPrivateKey(Context context) throws Exception{
|
||||
|
||||
try {
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0);
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
return privateKey;
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static NetworkPackage encrypt(NetworkPackage np, PublicKey publicKey) throws GeneralSecurityException, JSONException {
|
||||
|
||||
String serialized = np.serialize();
|
||||
|
||||
int chunkSize = 128;
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
JSONArray chunks = new JSONArray();
|
||||
while (serialized.length() > 0) {
|
||||
if (serialized.length() < chunkSize) {
|
||||
chunkSize = serialized.length();
|
||||
}
|
||||
String chunk = serialized.substring(0, chunkSize);
|
||||
serialized = serialized.substring(chunkSize);
|
||||
byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
|
||||
byte[] encryptedChunk;
|
||||
encryptedChunk = cipher.doFinal(chunkBytes);
|
||||
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
|
||||
|
||||
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED);
|
||||
encrypted.set("data", chunks);
|
||||
encrypted.setPayload(np.getPayload(), np.getPayloadSize());
|
||||
return encrypted;
|
||||
|
||||
}
|
||||
|
||||
public static NetworkPackage decrypt(NetworkPackage np, PrivateKey privateKey) throws GeneralSecurityException, JSONException {
|
||||
|
||||
JSONArray chunks = np.getJSONArray("data");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
String decryptedJson = "";
|
||||
for (int i = 0; i < chunks.length(); i++) {
|
||||
byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
|
||||
String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
|
||||
decryptedJson += decryptedChunk;
|
||||
}
|
||||
|
||||
NetworkPackage decrypted = NetworkPackage.unserialize(decryptedJson);
|
||||
decrypted.setPayload(np.getPayload(), np.getPayloadSize());
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Helpers.SecurityHelpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.spongycastle.asn1.x500.X500NameBuilder;
|
||||
import org.spongycastle.asn1.x500.style.BCStyle;
|
||||
import org.spongycastle.cert.X509CertificateHolder;
|
||||
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.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
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.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class SslHelper {
|
||||
|
||||
public enum SslMode{
|
||||
Client,
|
||||
Server
|
||||
}
|
||||
|
||||
public static X509Certificate certificate; //my device's certificate
|
||||
|
||||
public static final BouncyCastleProvider BC = new BouncyCastleProvider();
|
||||
|
||||
public static void initialiseCertificate(Context context){
|
||||
PrivateKey privateKey;
|
||||
PublicKey publicKey;
|
||||
|
||||
try {
|
||||
privateKey = RsaHelper.getPrivateKey(context);
|
||||
publicKey = RsaHelper.getPublicKey(context);
|
||||
}catch (Exception e){
|
||||
Log.e("SslHelper", "Error getting keys, can't create certificate");
|
||||
return;
|
||||
}
|
||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!settings.contains("certificate")) {
|
||||
try {
|
||||
|
||||
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
|
||||
nameBuilder.addRDN(BCStyle.CN, DeviceHelper.getDeviceId(context));
|
||||
nameBuilder.addRDN(BCStyle.OU, "KDE Connect");
|
||||
nameBuilder.addRDN(BCStyle.O, "KDE");
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.YEAR, -1);
|
||||
Date notBefore = calendar.getTime();
|
||||
calendar.add(Calendar.YEAR, 10);
|
||||
Date notAfter = calendar.getTime();
|
||||
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
|
||||
nameBuilder.build(),
|
||||
BigInteger.ONE,
|
||||
notBefore,
|
||||
notAfter,
|
||||
nameBuilder.build(),
|
||||
publicKey
|
||||
);
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privateKey);
|
||||
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner));
|
||||
|
||||
SharedPreferences.Editor edit = settings.edit();
|
||||
edit.putString("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
edit.apply();
|
||||
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initialiseCert", "Exception");
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
byte[] certificateBytes = Base64.decode(globalSettings.getString("certificate", ""), 0);
|
||||
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
|
||||
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/SslHelper", "Exception reading own certificate");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SSLContext getSslContext(Context context, String deviceId, boolean isDeviceTrusted) {
|
||||
try {
|
||||
// Get device private key
|
||||
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
||||
|
||||
// Get remote device certificate if trusted
|
||||
X509Certificate remoteDeviceCertificate = null;
|
||||
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);
|
||||
}
|
||||
|
||||
// Setup keystore
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null, null);
|
||||
keyStore.setKeyEntry("key", privateKey, "".toCharArray(), new Certificate[]{certificate});
|
||||
// Set certificate if device trusted
|
||||
if (remoteDeviceCertificate != null){
|
||||
keyStore.setCertificateEntry(deviceId, remoteDeviceCertificate);
|
||||
}
|
||||
|
||||
// Setup key manager factory
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
keyManagerFactory.init(keyStore, "".toCharArray());
|
||||
|
||||
|
||||
// Setup default trust manager
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(keyStore);
|
||||
|
||||
// Setup custom trust manager if device not trusted
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
SSLContext tlsContext = SSLContext.getInstance("TLSv1"); //Newer TLS versions are only supported on API 16+
|
||||
if (isDeviceTrusted) {
|
||||
tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
|
||||
}else {
|
||||
tlsContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, RandomHelper.secureRandom);
|
||||
}
|
||||
return tlsContext;
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/SslHelper", "Error creating tls context");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public static SSLEngine getSslEngine(final Context context, final String deviceId, SslMode sslMode) {
|
||||
|
||||
try{
|
||||
|
||||
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");
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
public static String getCertificateHash(Certificate certificate) {
|
||||
try {
|
||||
byte[] hash = MessageDigest.getInstance("SHA-1").digest(certificate.getEncoded());
|
||||
Formatter formatter = new Formatter();
|
||||
int i;
|
||||
for (i = 0; i < hash.length-1; i++) {
|
||||
formatter.format("%02x:", hash[i]);
|
||||
}
|
||||
formatter.format("%02x", hash[i]);
|
||||
return formatter.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Certificate parseCertificate(byte[] certificateBytes) throws IOException, CertificateException {
|
||||
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
|
||||
return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
}
|
@@ -21,6 +21,10 @@
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
@@ -30,28 +34,34 @@ import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class NetworkPackage {
|
||||
|
||||
public final static int ProtocolVersion = 6;
|
||||
public final static int ProtocolVersion = 5;
|
||||
|
||||
//TODO: Move these to their respective plugins
|
||||
public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity";
|
||||
public final static String PACKAGE_TYPE_PAIR = "kdeconnect.pair";
|
||||
public final static String PACKAGE_TYPE_ENCRYPTED = "kdeconnect.encrypted";
|
||||
public final static String PACKAGE_TYPE_CAPABILITIES = "kdeconnect.capabilities";
|
||||
|
||||
public static Set<String> protocolPackageTypes = new HashSet<String>() {{
|
||||
add(PACKAGE_TYPE_IDENTITY);
|
||||
add(PACKAGE_TYPE_PAIR);
|
||||
add(PACKAGE_TYPE_ENCRYPTED);
|
||||
add(PACKAGE_TYPE_CAPABILITIES);
|
||||
}};
|
||||
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
|
||||
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
|
||||
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
||||
public final static String PACKAGE_TYPE_SFTP = "kdeconnect.sftp";
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
|
||||
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
|
||||
public final static String PACKAGE_TYPE_MOUSEPAD = "kdeconnect.mousepad";
|
||||
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
|
||||
private long mId;
|
||||
String mType;
|
||||
private String mType;
|
||||
private JSONObject mBody;
|
||||
private InputStream mPayload;
|
||||
private JSONObject mPayloadTransferInfo;
|
||||
@@ -130,37 +140,109 @@ public class NetworkPackage {
|
||||
|
||||
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); }
|
||||
|
||||
public String serialize() throws JSONException {
|
||||
public String serialize() {
|
||||
JSONObject jo = new JSONObject();
|
||||
jo.put("id", mId);
|
||||
jo.put("type", mType);
|
||||
jo.put("body", mBody);
|
||||
if (hasPayload()) {
|
||||
jo.put("payloadSize", mPayloadSize);
|
||||
jo.put("payloadTransferInfo", mPayloadTransferInfo);
|
||||
try {
|
||||
jo.put("id", mId);
|
||||
jo.put("type", mType);
|
||||
jo.put("body", mBody);
|
||||
if (hasPayload()) {
|
||||
jo.put("payloadSize", mPayloadSize);
|
||||
jo.put("payloadTransferInfo", mPayloadTransferInfo);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPackage", "Serialization exception");
|
||||
}
|
||||
|
||||
//QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
|
||||
String json = jo.toString().replace("\\/","/")+"\n";
|
||||
|
||||
if (!isEncrypted()) {
|
||||
//Log.e("NetworkPackage.serialize", json);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
static public NetworkPackage unserialize(String s) throws JSONException {
|
||||
static public NetworkPackage unserialize(String s) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage();
|
||||
JSONObject jo = new JSONObject(s);
|
||||
np.mId = jo.getLong("id");
|
||||
np.mType = jo.getString("type");
|
||||
np.mBody = jo.getJSONObject("body");
|
||||
if (jo.has("payloadSize")) {
|
||||
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
|
||||
np.mPayloadSize = jo.getLong("payloadSize");
|
||||
} else {
|
||||
np.mPayloadTransferInfo = new JSONObject();
|
||||
np.mPayloadSize = 0;
|
||||
try {
|
||||
JSONObject jo = new JSONObject(s);
|
||||
np.mId = jo.getLong("id");
|
||||
np.mType = jo.getString("type");
|
||||
np.mBody = jo.getJSONObject("body");
|
||||
if (jo.has("payloadSize")) {
|
||||
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
|
||||
np.mPayloadSize = jo.getLong("payloadSize");
|
||||
} else {
|
||||
np.mPayloadTransferInfo = new JSONObject();
|
||||
np.mPayloadSize = 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPackage", "Unserialization exception unserializing "+s);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!np.isEncrypted()) {
|
||||
//Log.e("NetworkPackage.unserialize", s);
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
public NetworkPackage encrypt(PublicKey publicKey) throws GeneralSecurityException {
|
||||
|
||||
String serialized = serialize();
|
||||
|
||||
int chunkSize = 128;
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
JSONArray chunks = new JSONArray();
|
||||
while (serialized.length() > 0) {
|
||||
if (serialized.length() < chunkSize) {
|
||||
chunkSize = serialized.length();
|
||||
}
|
||||
String chunk = serialized.substring(0, chunkSize);
|
||||
serialized = serialized.substring(chunkSize);
|
||||
byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
|
||||
byte[] encryptedChunk;
|
||||
encryptedChunk = cipher.doFinal(chunkBytes);
|
||||
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
|
||||
|
||||
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED);
|
||||
encrypted.set("data", chunks);
|
||||
encrypted.setPayload(mPayload, mPayloadSize);
|
||||
return encrypted;
|
||||
|
||||
}
|
||||
|
||||
public NetworkPackage decrypt(PrivateKey privateKey) throws GeneralSecurityException, JSONException {
|
||||
|
||||
JSONArray chunks = mBody.getJSONArray("data");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
String decryptedJson = "";
|
||||
for (int i = 0; i < chunks.length(); i++) {
|
||||
byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
|
||||
String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
|
||||
decryptedJson += decryptedChunk;
|
||||
}
|
||||
|
||||
NetworkPackage decrypted = unserialize(decryptedJson);
|
||||
decrypted.setPayload(mPayload, mPayloadSize);
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
static public NetworkPackage createIdentityPackage(Context context) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
|
||||
@@ -180,6 +262,21 @@ public class NetworkPackage {
|
||||
|
||||
}
|
||||
|
||||
|
||||
static public NetworkPackage createPublicKeyPackage(Context context) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
|
||||
np.set("pair", true);
|
||||
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
|
||||
np.set("publicKey", publicKey);
|
||||
|
||||
return np;
|
||||
|
||||
}
|
||||
|
||||
public void setPayload(byte[] data) {
|
||||
setPayload(new ByteArrayInputStream(data), data.length);
|
||||
}
|
||||
|
@@ -33,9 +33,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class BatteryPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
||||
public final static String PACKAGE_TYPE_BATTERY_REQUEST = "kdeconnect.battery.request";
|
||||
|
||||
// keep these fields in sync with kdeconnect-kded:BatteryPlugin.h:ThresholdBatteryEvent
|
||||
private static final int THRESHOLD_EVENT_NONE= 0;
|
||||
private static final int THRESHOLD_EVENT_BATTERY_LOW = 1;
|
||||
@@ -75,7 +72,7 @@ public class BatteryPlugin extends Plugin {
|
||||
|
||||
} else {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_BATTERY);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
|
||||
np.set("currentCharge", currentCharge);
|
||||
np.set("isCharging", isCharging);
|
||||
np.set("thresholdEvent", thresholdEvent);
|
||||
@@ -102,6 +99,7 @@ public class BatteryPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false;
|
||||
|
||||
if (np.getBoolean("request")) {
|
||||
if (lastInfo != null) {
|
||||
@@ -112,16 +110,4 @@ public class BatteryPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
String[] packetTypes = {PACKAGE_TYPE_BATTERY_REQUEST};
|
||||
return packetTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
String[] packetTypes = {PACKAGE_TYPE_BATTERY};
|
||||
return packetTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,18 +20,13 @@
|
||||
|
||||
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.content.ClipboardManager;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class ClipboardListener {
|
||||
|
||||
|
||||
@@ -47,33 +42,28 @@ public class ClipboardListener {
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
|
||||
@Override
|
||||
public void onPrimaryClipChanged() {
|
||||
try {
|
||||
public void onPrimaryClipChanged() {
|
||||
try {
|
||||
|
||||
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
|
||||
String content = item.coerceToText(context).toString();
|
||||
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
|
||||
String content = item.coerceToText(context).toString();
|
||||
|
||||
if (!content.equals(currentContent)) {
|
||||
NetworkPackage np = new NetworkPackage(ClipboardPlugin.PACKAGE_TYPE_CLIPBOARD);
|
||||
np.set("content", content);
|
||||
device.sendPackage(np);
|
||||
currentContent = content;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
//Probably clipboard was not text
|
||||
}
|
||||
if (!content.equals(currentContent)) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CLIPBOARD);
|
||||
np.set("content", content);
|
||||
device.sendPackage(np);
|
||||
currentContent = content;
|
||||
}
|
||||
};
|
||||
cm.addPrimaryClipChangedListener(listener);
|
||||
|
||||
} catch(Exception e) {
|
||||
//Probably clipboard was not text
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
cm.addPrimaryClipChangedListener(listener);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
|
@@ -20,14 +20,18 @@
|
||||
|
||||
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class ClipboardPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_clipboard);
|
||||
@@ -59,23 +63,13 @@ public class ClipboardPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_CLIPBOARD)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String content = np.getString("content");
|
||||
listener.setText(content);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
String[] packetTypes = {PACKAGE_TYPE_CLIPBOARD};
|
||||
return packetTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
String[] packetTypes = {PACKAGE_TYPE_CLIPBOARD};
|
||||
return packetTypes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,55 +0,0 @@
|
||||
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class FindMyPhoneActivity extends Activity {
|
||||
Ringtone ringtone;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_find_my_phone);
|
||||
|
||||
Window window = this.getWindow();
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
|
||||
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||
|
||||
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
|
||||
ringtone = RingtoneManager.getRingtone(getApplicationContext(), ringtoneUri);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
||||
AudioAttributes.Builder b = new AudioAttributes.Builder();
|
||||
b.setUsage(AudioAttributes.USAGE_ALARM);
|
||||
ringtone.setAudioAttributes(b.build());
|
||||
} else {
|
||||
ringtone.setStreamType(AudioManager.STREAM_ALARM);
|
||||
}
|
||||
|
||||
ringtone.play();
|
||||
|
||||
findViewById(R.id.bFindMyPhone).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
ringtone.stop();
|
||||
super.finish();
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
|
||||
/**
|
||||
* Created by vineet on 1/11/14.
|
||||
* and David Edmundson 2015
|
||||
*/
|
||||
public class FindMyPhonePlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_FINDMYPHONE = "kdeconnect.findmyphone";
|
||||
public final static String PACKAGE_TYPE_FINDMYPHONE_REQUEST = "kdeconnect.findmyphone.request";
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getString(R.string.findmyphone_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getString(R.string.findmyphone_description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
|
||||
Intent intent = new Intent(context,FindMyPhoneActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_FINDMYPHONE_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
@@ -1,6 +1,10 @@
|
||||
package org.kde.kdeconnect.Plugins.MousePadPlugin;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
public class KeyInputConnection extends BaseInputConnection {
|
||||
private KeyListenerView view;
|
||||
|
@@ -101,7 +101,7 @@ public class KeyListenerView extends View {
|
||||
}
|
||||
|
||||
public void sendChars(CharSequence chars) {
|
||||
final NetworkPackage np = new NetworkPackage(MousePadPlugin.PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
final NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("key", chars.toString());
|
||||
sendKeyPressPackage(np);
|
||||
}
|
||||
@@ -135,7 +135,7 @@ public class KeyListenerView extends View {
|
||||
//Log.e("KeyDown", "utfChar:" + (char)event.getUnicodeChar());
|
||||
//Log.e("KeyDown", "intUtfChar:" + event.getUnicodeChar());
|
||||
|
||||
final NetworkPackage np = new NetworkPackage(MousePadPlugin.PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
final NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
|
||||
boolean modifier = false;
|
||||
if (event.isAltPressed()) {
|
||||
|
@@ -28,18 +28,19 @@ import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
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;
|
||||
@@ -48,8 +49,6 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
private float mPrevY;
|
||||
private float mCurrentX;
|
||||
private float mCurrentY;
|
||||
private float mCurrentSensitivity;
|
||||
private int scrollDirection = 1;
|
||||
|
||||
boolean isScrolling = false;
|
||||
float accumulatedDistanceY = 0;
|
||||
@@ -90,42 +89,14 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
keyListenerView.setDeviceId(deviceId);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (prefs.getBoolean(getString(R.string.mousepad_scroll_direction),false)) {
|
||||
scrollDirection = -1;
|
||||
} else {
|
||||
scrollDirection = 1;
|
||||
}
|
||||
String doubleTapSetting = prefs.getString(getString(R.string.mousepad_double_tap_key),
|
||||
getString(R.string.mousepad_double_default));
|
||||
String tripleTapSetting = prefs.getString(getString(R.string.mousepad_triple_tap_key),
|
||||
getString(R.string.mousepad_triple_default));
|
||||
String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key),
|
||||
getString(R.string.mousepad_sensitivity_default));
|
||||
|
||||
doubleTapAction = ClickType.fromString(doubleTapSetting);
|
||||
tripleTapAction = ClickType.fromString(tripleTapSetting);
|
||||
|
||||
switch (sensitivitySetting){
|
||||
case "slowest":
|
||||
mCurrentSensitivity = 0.2f;
|
||||
break;
|
||||
case "aboveSlowest":
|
||||
mCurrentSensitivity = 0.5f;
|
||||
break;
|
||||
case "default":
|
||||
mCurrentSensitivity = 1.0f;
|
||||
break;
|
||||
case "aboveDefault":
|
||||
mCurrentSensitivity = 1.5f;
|
||||
break;
|
||||
case "fastest":
|
||||
mCurrentSensitivity = 2.0f;
|
||||
break;
|
||||
default:
|
||||
mCurrentSensitivity = 1.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
final View decorView = getWindow().getDecorView();
|
||||
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@@ -213,7 +184,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY, mCurrentSensitivity);
|
||||
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY);
|
||||
mPrevX = mCurrentX;
|
||||
mPrevY = mCurrentY;
|
||||
}
|
||||
@@ -258,7 +229,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendScroll(0, scrollDirection * scrollToSendY);
|
||||
mousePadPlugin.sendScroll(0, scrollToSendY);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -31,9 +31,6 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class MousePadPlugin extends Plugin {
|
||||
|
||||
//public final static String PACKAGE_TYPE_MOUSEPAD = "kdeconnect.mousepad";
|
||||
public final static String PACKAGE_TYPE_MOUSEPAD_REQUEST = "kdeconnect.mousepad.request";
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getString(R.string.pref_plugin_mousepad);
|
||||
@@ -66,66 +63,50 @@ public class MousePadPlugin extends Plugin {
|
||||
parentActivity.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_MOUSEPAD_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionName() {
|
||||
return context.getString(R.string.open_mousepad);
|
||||
}
|
||||
|
||||
public void sendMouseDelta(float dx, float dy, float sensitivity) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
|
||||
if (sensitivity <= 0.0001f) {
|
||||
sensitivity = 1.0f;
|
||||
}
|
||||
|
||||
np.set("dx", dx*sensitivity);
|
||||
np.set("dy", dy*sensitivity);
|
||||
|
||||
public void sendMouseDelta(float dx, float dy) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("dx", dx);
|
||||
np.set("dy", dy);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendSingleClick() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("singleclick", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendDoubleClick() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("doubleclick", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendMiddleClick() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("middleclick", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendRightClick() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("rightclick", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendSingleHold(){
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("singlehold", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void sendScroll(float dx, float dy) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
|
||||
np.set("scroll", true);
|
||||
np.set("dx", dx);
|
||||
np.set("dy", dy);
|
||||
|
@@ -38,9 +38,9 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
@@ -89,11 +89,7 @@ public class MprisActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void run() {
|
||||
String song = mpris.getCurrentSong();
|
||||
|
||||
TextView nowPlaying = (TextView) findViewById(R.id.now_playing_textview);
|
||||
if (!nowPlaying.getText().toString().equals(song)) {
|
||||
nowPlaying.setText(song);
|
||||
}
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText(song);
|
||||
|
||||
if (mpris.getLength() > -1 && mpris.getPosition() > -1 && !"spotify".equals(mpris.getPlayer().toLowerCase())) {
|
||||
((TextView) findViewById(R.id.time_textview)).setText(milisToProgress(mpris.getLength()));
|
||||
@@ -414,13 +410,10 @@ public class MprisActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device != null) {
|
||||
MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
|
||||
if (mpris != null) {
|
||||
positionSeek.setProgress((int) (mpris.getPosition()));
|
||||
}
|
||||
MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
|
||||
if (mpris != null) {
|
||||
positionSeek.setProgress((int) (mpris.getPosition()));
|
||||
}
|
||||
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
|
||||
}
|
||||
});
|
||||
@@ -457,7 +450,6 @@ public class MprisActivity extends ActionBarActivity {
|
||||
|
||||
});
|
||||
|
||||
findViewById(R.id.now_playing_textview).setSelected(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -37,9 +37,6 @@ import java.util.HashMap;
|
||||
|
||||
public class MprisPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
|
||||
public final static String PACKAGE_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
|
||||
|
||||
private String player = "";
|
||||
private boolean playing = false;
|
||||
private String currentSong = "";
|
||||
@@ -85,7 +82,7 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public void sendAction(String player, String action) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("action", action);
|
||||
device.sendPackage(np);
|
||||
@@ -95,14 +92,14 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public void setVolume(int volume) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("setVolume",volume);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("SetPosition", position);
|
||||
device.sendPackage(np);
|
||||
@@ -111,7 +108,7 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public void Seek(int offset) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("Seek", offset);
|
||||
device.sendPackage(np);
|
||||
@@ -119,6 +116,7 @@ public class MprisPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false;
|
||||
|
||||
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") || np.has("pos")) {
|
||||
if (np.getString("player").equals(player)) {
|
||||
@@ -172,16 +170,6 @@ public class MprisPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[] {PACKAGE_TYPE_MPRIS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[] {PACKAGE_TYPE_MPRIS_REQUEST};
|
||||
}
|
||||
|
||||
public void setPlayerStatusUpdatedHandler(String id, Handler h) {
|
||||
playerStatusUpdated.put(id, h);
|
||||
|
||||
@@ -253,13 +241,13 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private void requestPlayerList() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("requestPlayerList",true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
private void requestPlayerStatus() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player",player);
|
||||
np.set("requestNowPlaying",true);
|
||||
np.set("requestVolume",true);
|
||||
|
@@ -23,8 +23,8 @@ package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
@@ -80,14 +80,11 @@ public class NotificationReceiver extends NotificationListenerService {
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
//Log.e("NotificationReceiver", "onStartCommand");
|
||||
mutex.lock();
|
||||
try {
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
||||
}
|
||||
callbacks.clear();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
||||
}
|
||||
callbacks.clear();
|
||||
mutex.unlock();
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
@@ -99,11 +96,8 @@ public class NotificationReceiver extends NotificationListenerService {
|
||||
public static void RunCommand(Context c, final InstanceCallback callback) {
|
||||
if (callback != null) {
|
||||
mutex.lock();
|
||||
try {
|
||||
callbacks.add(callback);
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
callbacks.add(callback);
|
||||
mutex.unlock();
|
||||
}
|
||||
Intent serviceIntent = new Intent(c, NotificationReceiver.class);
|
||||
c.startService(serviceIntent);
|
||||
|
@@ -24,36 +24,23 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
|
||||
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
|
||||
|
||||
/*
|
||||
private boolean sendIcons = false;
|
||||
*/
|
||||
@@ -90,52 +77,54 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
if (hasPermission()) {
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
try {
|
||||
service.addListener(NotificationsPlugin.this);
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("NotificationsPlugin", "Exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasPermission()) {
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
try {
|
||||
service.addListener(NotificationsPlugin.this);
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("NotificationsPlugin", "Exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// request all existing notifications
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
|
||||
np.set("request", true);
|
||||
device.sendPackage(np);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
service.removeListener(NotificationsPlugin.this);
|
||||
}
|
||||
});
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
service.removeListener(NotificationsPlugin.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
|
||||
String id = getNotificationKeyCompat(statusBarNotification);
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
|
||||
np.set("id", id);
|
||||
np.set("isCancel", true);
|
||||
device.sendPackage(np);
|
||||
@@ -177,19 +166,12 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
return;
|
||||
}
|
||||
|
||||
if ("com.android.systemui".equals(packageName) &&
|
||||
"low_battery".equals(statusBarNotification.getTag()))
|
||||
{
|
||||
//HACK: Android low battery notification are posted again every few seconds. Ignore them, as we already have a battery indicator.
|
||||
return;
|
||||
}
|
||||
|
||||
if (packageName.equals("com.google.android.googlequicksearchbox")) {
|
||||
//HACK: Hide Google Now notifications that keep constantly popping up (and without text because we don't know how to read them properly)
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION);
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
|
||||
|
||||
if (packageName.equals("org.kde.kdeconnect_tp"))
|
||||
{
|
||||
@@ -272,6 +254,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(final NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
|
||||
/*
|
||||
if (np.getBoolean("sendIcons")) {
|
||||
sendIcons = true;
|
||||
@@ -279,103 +262,53 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
*/
|
||||
if (np.getBoolean("request")) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
private void sendCurrentNotifications(NotificationReceiver service) {
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
}
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
private void sendCurrentNotifications(NotificationReceiver service) {
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onServiceStart(final NotificationReceiver service) {
|
||||
try {
|
||||
//If service just started, this call will throw an exception because the answer is not ready yet
|
||||
sendCurrentNotifications(service);
|
||||
} catch(Exception e) {
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying in 100ms...");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
|
||||
sendCurrentNotifications(service);
|
||||
} catch (Exception e) {
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
@Override
|
||||
public void onServiceStart(final NotificationReceiver service) {
|
||||
try {
|
||||
//If service just started, this call will throw an exception because the answer is not ready yet
|
||||
sendCurrentNotifications(service);
|
||||
} catch(Exception e) {
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying in 100ms...");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
|
||||
sendCurrentNotifications(service);
|
||||
} catch (Exception e) {
|
||||
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} else if (np.has("cancel")) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
String dismissedId = np.getString("cancel");
|
||||
cancelNotificationCompat(service, dismissedId);
|
||||
}
|
||||
});
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
String dismissedId = np.getString("cancel");
|
||||
cancelNotificationCompat(service, dismissedId);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (!np.has("ticker") || !np.has("appName") || !np.has("id")) {
|
||||
Log.e("NotificationsPlugin", "Received notification package lacks properties");
|
||||
} else {
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MaterialActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
Bitmap largeIcon = null;
|
||||
if (np.hasPayload()) {
|
||||
int width = 64; // default icon dimensions
|
||||
int height = 64;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
|
||||
height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
|
||||
}
|
||||
final InputStream input = np.getPayload();
|
||||
largeIcon = BitmapFactory.decodeStream(np.getPayload());
|
||||
try { input.close(); } catch (Exception e) { }
|
||||
if (largeIcon != null) {
|
||||
//Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height);
|
||||
if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) {
|
||||
// older API levels don't scale notification icons automatically, therefore:
|
||||
largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(np.getString("appName"))
|
||||
.setContentText(np.getString("ticker"))
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(np.getString("ticker"))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setAutoCancel(true)
|
||||
.setLocalOnly(true) // to avoid bouncing the notification back to other kdeconnect nodes
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
Log.w("NotificationsPlugin", "Nothing to do");
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
try {
|
||||
// tag all incoming notifications
|
||||
notificationManager.notify("kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
|
||||
} catch (Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -417,26 +350,12 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
|
||||
}
|
||||
|
||||
//For compat with API<21, because lollipop changed the way to cancel notifications
|
||||
//For compat with API<21, because lollipop changed they way to cancel notifications
|
||||
public static void cancelNotificationCompat(NotificationReceiver service, String compatKey) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
service.cancelNotification(compatKey);
|
||||
} else {
|
||||
int first = compatKey.indexOf(':');
|
||||
if (first == -1) {
|
||||
Log.e("cancelNotificationCompa","Not formated like a notification key: "+ compatKey);
|
||||
return;
|
||||
}
|
||||
int last = compatKey.lastIndexOf(':');
|
||||
String packageName = compatKey.substring(0, first);
|
||||
String tag = compatKey.substring(first + 1, last);
|
||||
@@ -453,20 +372,16 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
}
|
||||
|
||||
public static String getNotificationKeyCompat(StatusBarNotification statusBarNotification) {
|
||||
String result;
|
||||
// first check if it's one of our remoteIds
|
||||
String tag = statusBarNotification.getTag();
|
||||
if (tag != null && tag.startsWith("kdeconnectId:"))
|
||||
result = Integer.toString(statusBarNotification.getId());
|
||||
else if (Build.VERSION.SDK_INT >= 21) {
|
||||
result = statusBarNotification.getKey();
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return statusBarNotification.getKey();
|
||||
} else {
|
||||
String packageName = statusBarNotification.getPackageName();
|
||||
String tag = statusBarNotification.getTag();
|
||||
int id = statusBarNotification.getId();
|
||||
String safePackageName = (packageName == null) ? "" : packageName;
|
||||
String safeTag = (tag == null) ? "" : tag;
|
||||
result = safePackageName + ":" + safeTag + ":" + id;
|
||||
return safePackageName + ":" + safeTag + ":" + id;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,18 +28,15 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MaterialActivity;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
|
||||
public class PingPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_ping);
|
||||
@@ -53,51 +50,50 @@ public class PingPlugin extends Plugin {
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
|
||||
if (!np.getType().equals(PACKAGE_TYPE_PING)) {
|
||||
Log.e("PingPlugin", "Ping plugin should not receive packets other than pings!");
|
||||
return false;
|
||||
//Log.e("PingPackageReceiver", "onPackageReceived");
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
|
||||
//Log.e("PingPackageReceiver", "was a ping!");
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MaterialActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
int id;
|
||||
String message;
|
||||
if (np.has("message")) {
|
||||
message = np.getString("message");
|
||||
id = (int)System.currentTimeMillis();
|
||||
} else {
|
||||
message = "Ping!";
|
||||
id = 42; //A unique id to create only one notification
|
||||
}
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(device.getName())
|
||||
.setContentText(message)
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(message)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
try {
|
||||
notificationManager.notify(id, noti);
|
||||
} catch(Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
//Log.e("PingPackageReceiver", "was a ping!");
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MaterialActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
int id;
|
||||
String message;
|
||||
if (np.has("message")) {
|
||||
message = np.getString("message");
|
||||
id = (int)System.currentTimeMillis();
|
||||
} else {
|
||||
message = "Ping!";
|
||||
id = 42; //A unique id to create only one notification
|
||||
}
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(device.getName())
|
||||
.setContentText(message)
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(message)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
try {
|
||||
notificationManager.notify(id, noti);
|
||||
} catch(Exception e) {
|
||||
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +104,7 @@ public class PingPlugin extends Plugin {
|
||||
@Override
|
||||
public void startMainActivity(Activity activity) {
|
||||
if (device != null) {
|
||||
device.sendPackage(new NetworkPackage(PACKAGE_TYPE_PING));
|
||||
device.sendPackage(new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,15 +117,4 @@ public class PingPlugin extends Plugin {
|
||||
public boolean displayInContextMenu() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_PING};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_PING};
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -174,16 +174,6 @@ public abstract class Plugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return the list of NetworkPackage types that this plugin can handle
|
||||
*/
|
||||
public abstract String[] getSupportedPackageTypes();
|
||||
|
||||
/**
|
||||
* Should return the list of NetworkPackage types that this plugin can send
|
||||
*/
|
||||
public abstract String[] getOutgoingPackageTypes();
|
||||
|
||||
/**
|
||||
* Creates a button that will be displayed in the user interface
|
||||
* It can open an activity or perform any other action that the
|
||||
|
@@ -26,42 +26,32 @@ import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhonePlugin;
|
||||
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
|
||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
|
||||
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class PluginFactory {
|
||||
|
||||
public static class PluginInfo {
|
||||
|
||||
public PluginInfo(String displayName, String description, Drawable icon,
|
||||
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired,
|
||||
String[] supportedPackageTypes, String[] outgoingPackageTypes) {
|
||||
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired) {
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
this.icon = icon;
|
||||
this.enabledByDefault = enabledByDefault;
|
||||
this.hasSettings = hasSettings;
|
||||
this.listenToUnpaired = listenToUnpaired;
|
||||
HashSet<String> incoming = new HashSet<>();
|
||||
if (supportedPackageTypes != null) Collections.addAll(incoming, supportedPackageTypes);
|
||||
this.supportedPackageTypes = Collections.unmodifiableSet(incoming);
|
||||
HashSet<String> outgoing = new HashSet<>();
|
||||
if (outgoingPackageTypes != null) Collections.addAll(outgoing, outgoingPackageTypes);
|
||||
this.outgoingPackageTypes = Collections.unmodifiableSet(outgoing);
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
@@ -86,23 +76,12 @@ public class PluginFactory {
|
||||
return listenToUnpaired;
|
||||
}
|
||||
|
||||
public Set<String> getOutgoingPackageTypes() {
|
||||
return outgoingPackageTypes;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedPackageTypes() {
|
||||
return supportedPackageTypes;
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
private final Drawable icon;
|
||||
private final boolean enabledByDefault;
|
||||
private final boolean hasSettings;
|
||||
private final boolean listenToUnpaired;
|
||||
private final Set<String> supportedPackageTypes;
|
||||
private final Set<String> outgoingPackageTypes;
|
||||
|
||||
}
|
||||
|
||||
private static final Map<String, Class> availablePlugins = new TreeMap<>();
|
||||
@@ -119,24 +98,16 @@ public class PluginFactory {
|
||||
PluginFactory.registerPlugin(NotificationsPlugin.class);
|
||||
PluginFactory.registerPlugin(MousePadPlugin.class);
|
||||
PluginFactory.registerPlugin(SharePlugin.class);
|
||||
//PluginFactory.registerPlugin(TelepathyPlugin.class);
|
||||
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
|
||||
PluginFactory.registerPlugin(RunCommandPlugin.class);
|
||||
}
|
||||
|
||||
public static PluginInfo getPluginInfo(Context context, String pluginKey) {
|
||||
|
||||
PluginInfo info = availablePluginsInfo.get(pluginKey); //Is it cached?
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
|
||||
if (info != null) return info;
|
||||
try {
|
||||
Plugin p = ((Plugin)availablePlugins.get(pluginKey).newInstance());
|
||||
p.setContext(context, null);
|
||||
info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(),
|
||||
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices(),
|
||||
p.getSupportedPackageTypes(), p.getOutgoingPackageTypes());
|
||||
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices());
|
||||
availablePluginsInfo.put(pluginKey, info); //Cache it
|
||||
return info;
|
||||
} catch(Exception e) {
|
||||
@@ -144,7 +115,6 @@ public class PluginFactory {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Set<String> getAvailablePlugins() {
|
||||
|
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.List.EntryItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RunCommandActivity extends ActionBarActivity {
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private void updateView() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ListView view = (ListView) findViewById(R.id.listView1);
|
||||
|
||||
final ArrayList<JSONObject> commands = plugin.getCommandList();
|
||||
|
||||
ArrayList<ListAdapter.Item> commandItems = new ArrayList<>();
|
||||
for (JSONObject obj : commands) {
|
||||
try {
|
||||
commandItems.add(new EntryItem(obj.getString("name"), obj.getString("command")));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
|
||||
|
||||
view.setAdapter(adapter);
|
||||
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
try {
|
||||
plugin.runCommand(commands.get(i).getString("key"));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final RunCommandPlugin.CommandsChangedCallback theCallback = new RunCommandPlugin.CommandsChangedCallback() {
|
||||
@Override
|
||||
public void update() {
|
||||
updateView();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_list);
|
||||
|
||||
deviceId = getIntent().getStringExtra("deviceId");
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.addCommandsUpdatedCallback(theCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.removeCommandsUpdatedCallback(theCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class RunCommandPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_RUNCOMMAND = "kdeconnect.runcommand";
|
||||
public final static String PACKAGE_TYPE_RUNCOMMAND_REQUEST = "kdeconnect.runcommand.request";
|
||||
|
||||
private ArrayList<JSONObject> commandList = new ArrayList<>();
|
||||
private ArrayList<CommandsChangedCallback> callbacks = new ArrayList<>();
|
||||
|
||||
public void addCommandsUpdatedCallback(CommandsChangedCallback newCallback) {
|
||||
callbacks.add(newCallback);
|
||||
}
|
||||
|
||||
public void removeCommandsUpdatedCallback(CommandsChangedCallback theCallback) {
|
||||
callbacks.remove(theCallback);
|
||||
}
|
||||
|
||||
interface CommandsChangedCallback {
|
||||
void update();
|
||||
};
|
||||
|
||||
public ArrayList<JSONObject> getCommandList() {
|
||||
return commandList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_runcommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getResources().getString(R.string.pref_plugin_runcommand_desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getIcon() {
|
||||
return ContextCompat.getDrawable(context, R.drawable.runcommand_plugin_icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
requestCommandList();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
|
||||
if (np.has("commandList")) {
|
||||
commandList.clear();
|
||||
try {
|
||||
JSONObject obj = new JSONObject(np.getString("commandList"));
|
||||
Iterator<String> keys = obj.keys();
|
||||
while(keys.hasNext()){
|
||||
String s = keys.next();
|
||||
JSONObject o = obj.getJSONObject(s);
|
||||
o.put("key", s);
|
||||
commandList.add(o);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (CommandsChangedCallback aCallback : callbacks) {
|
||||
aCallback.update();
|
||||
}
|
||||
|
||||
device.onPluginsChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_RUNCOMMAND};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_RUNCOMMAND_REQUEST};
|
||||
}
|
||||
|
||||
public void runCommand(String cmdKey) {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
|
||||
np.set("key", cmdKey);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
private void requestCommandList() {
|
||||
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
|
||||
np.set("requestCommandList", true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainActivity() {
|
||||
return !commandList.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMainActivity(Activity parentActivity) {
|
||||
Intent intent = new Intent(parentActivity, RunCommandActivity.class);
|
||||
intent.putExtra("deviceId", device.getDeviceId());
|
||||
parentActivity.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionName() {
|
||||
return context.getString(R.string.pref_plugin_runcommand);
|
||||
}
|
||||
|
||||
}
|
@@ -23,10 +23,10 @@ package org.kde.kdeconnect.Plugins.SftpPlugin;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.Session;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.FileSystemFactory;
|
||||
import org.apache.sshd.server.FileSystemView;
|
||||
@@ -36,28 +36,59 @@ import org.apache.sshd.server.SshFile;
|
||||
import org.apache.sshd.server.command.ScpCommandFactory;
|
||||
import org.apache.sshd.server.filesystem.NativeFileSystemView;
|
||||
import org.apache.sshd.server.filesystem.NativeSshFile;
|
||||
import org.apache.sshd.server.kex.DHG1;
|
||||
import org.apache.sshd.server.kex.DHG14;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.sftp.SftpSubsystem;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
public void setUser(String user) {this.user = user;}
|
||||
public String getUser() {return this.user;}
|
||||
|
||||
public void setPassword(String password) {this.password = password;}
|
||||
public String getPassword() {return this.password;}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, String password, ServerSession session) {
|
||||
return user.equals(this.user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
private String user;
|
||||
private String password;
|
||||
}
|
||||
|
||||
class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
|
||||
|
||||
private final List<PublicKey> keys = new ArrayList<>();
|
||||
|
||||
public void addKey(PublicKey key) {
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, PublicKey key, ServerSession session) {
|
||||
for (PublicKey k : keys) {
|
||||
if (key.equals(k)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SimpleSftpServer {
|
||||
private static final int STARTPORT = 1739;
|
||||
private static final int ENDPORT = 1764;
|
||||
@@ -69,36 +100,27 @@ class SimpleSftpServer {
|
||||
|
||||
public final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
|
||||
public final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
|
||||
|
||||
static {
|
||||
Security.insertProviderAt(SslHelper.BC, 1);
|
||||
SecurityUtils.setRegisterBouncyCastle(false);
|
||||
}
|
||||
private final SshServer sshd = SshServer.setUpDefaultServer();
|
||||
|
||||
public void init(Context ctx, Device device) {
|
||||
sshd.setKeyExchangeFactories(Arrays.asList(
|
||||
new DHG14.Factory(),
|
||||
new DHG1.Factory()));
|
||||
|
||||
public void init(Context ctx, Device device) {
|
||||
passwordAuth.setUser(USER);
|
||||
keyAuth.addKey(device.publicKey);
|
||||
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(ctx.getFilesDir() + "/sftpd.ser"));
|
||||
|
||||
//sshd.setFileSystemFactory(new NativeFileSystemFactory());
|
||||
sshd.setFileSystemFactory(new SecureFileSystemFactory());
|
||||
//sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
|
||||
sshd.setCommandFactory(new ScpCommandFactory());
|
||||
sshd.setSubsystemFactories(Collections.singletonList((NamedFactory<Command>)new SftpSubsystem.Factory()));
|
||||
|
||||
if (device.publicKey != null) {
|
||||
keyAuth.addKey(device.publicKey);
|
||||
sshd.setPublickeyAuthenticator(keyAuth);
|
||||
}
|
||||
sshd.setPasswordAuthenticator(passwordAuth);
|
||||
sshd.setPublickeyAuthenticator(keyAuth);
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
if (!started) {
|
||||
|
||||
String password = RandomHelper.randomString(28);
|
||||
String password = Long.toHexString(Double.doubleToLongBits(Math.random()));
|
||||
passwordAuth.setPassword(password);
|
||||
|
||||
port = STARTPORT;
|
||||
@@ -108,7 +130,6 @@ class SimpleSftpServer {
|
||||
sshd.start();
|
||||
started = true;
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
port++;
|
||||
if (port >= ENDPORT) {
|
||||
port = -1;
|
||||
@@ -126,8 +147,8 @@ class SimpleSftpServer {
|
||||
try {
|
||||
started = false;
|
||||
sshd.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +161,7 @@ class SimpleSftpServer {
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
if (!inetAddress.isLoopbackAddress()) {
|
||||
String address = inetAddress.getHostAddress();
|
||||
if(inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
|
||||
if (InetAddressUtils.isIPv4Address(address)) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
|
||||
return address;
|
||||
} else {
|
||||
ip6 = address;
|
||||
@@ -153,18 +174,20 @@ class SimpleSftpServer {
|
||||
return ip6;
|
||||
}
|
||||
|
||||
static class SecureFileSystemFactory implements FileSystemFactory {
|
||||
}
|
||||
|
||||
class SecureFileSystemFactory implements FileSystemFactory {
|
||||
|
||||
public SecureFileSystemFactory() {}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public FileSystemView createFileSystemView(final Session username) {
|
||||
final String base = "/";
|
||||
return new SecureFileSystemView(base, username.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
static class SecureFileSystemView extends NativeFileSystemView {
|
||||
class SecureFileSystemView extends NativeFileSystemView {
|
||||
// the first and the last character will always be '/'
|
||||
// It is always with respect to the root directory.
|
||||
private String currDir = "/";
|
||||
@@ -200,47 +223,8 @@ class SimpleSftpServer {
|
||||
}
|
||||
}
|
||||
|
||||
static class SecureSshFile extends NativeSshFile {
|
||||
class SecureSshFile extends NativeSshFile {
|
||||
public SecureSshFile(final String fileName, final File file, final String userName) {
|
||||
super(fileName, file, userName);
|
||||
}
|
||||
}
|
||||
|
||||
static class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
public void setUser(String user) {this.user = user;}
|
||||
public String getUser() {return this.user;}
|
||||
|
||||
public void setPassword(String password) {this.password = password;}
|
||||
public String getPassword() {return this.password;}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, String password, ServerSession session) {
|
||||
return user.equals(this.user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
private String user;
|
||||
private String password;
|
||||
}
|
||||
|
||||
static class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
|
||||
|
||||
private final List<PublicKey> keys = new ArrayList<>();
|
||||
|
||||
public void addKey(PublicKey key) {
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, PublicKey key, ServerSession session) {
|
||||
for (PublicKey k : keys) {
|
||||
if (key.equals(k)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -33,9 +33,6 @@ import java.util.List;
|
||||
|
||||
public class SftpPlugin extends Plugin {
|
||||
|
||||
public final static String PACKAGE_TYPE_SFTP = "kdeconnect.sftp";
|
||||
public final static String PACKAGE_TYPE_SFTP_REQUEST = "kdeconnect.sftp.request";
|
||||
|
||||
private static final SimpleSftpServer server = new SimpleSftpServer();
|
||||
|
||||
@Override
|
||||
@@ -61,11 +58,12 @@ public class SftpPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SFTP)) return false;
|
||||
|
||||
if (np.getBoolean("startBrowsing")) {
|
||||
if (server.start()) {
|
||||
|
||||
NetworkPackage np2 = new NetworkPackage(PACKAGE_TYPE_SFTP);
|
||||
NetworkPackage np2 = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SFTP);
|
||||
|
||||
np2.set("ip", server.getLocalIpAddress());
|
||||
np2.set("port", server.port);
|
||||
@@ -129,14 +127,4 @@ public class SftpPlugin extends Plugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_SFTP_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPackageTypes() {
|
||||
return new String[]{PACKAGE_TYPE_SFTP};
|
||||
}
|
||||
|
||||
}
|
||||
|