2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-02 07:05:09 +00:00

Compare commits

...

161 Commits
v0.3 ... v0.7.2

Author SHA1 Message Date
Albert Vaca
acdf8d467e This has to be public for some reason 2014-07-12 20:03:08 +02:00
Albert Vaca
ceee1ee1f6 Increased version number to release 2014-07-12 20:03:08 +02:00
Albert Vaca
071f71cb9c Silly changes proposed by lint 2014-07-12 20:03:08 +02:00
Albert Vaca
4adf2cfbbd Updated project to new Android Build Tools. 2014-07-12 20:03:08 +02:00
l10n daemon script
de4fa31b2a SVN_SILENT made messages (after extraction) 2014-07-07 02:01:50 +00:00
l10n daemon script
4deba4ea4f SVN_SILENT made messages (after extraction) 2014-07-05 01:59:05 +00:00
l10n daemon script
07a8b107eb SVN_SILENT made messages (after extraction) 2014-07-03 01:51:49 +00:00
Ahmed I. Khalil
3b750c78ab Added Triple and Double finger gestures.
For right and middle clicking.

REVIEW: 119072
2014-07-02 17:19:44 +02:00
l10n daemon script
47790a710a SVN_SILENT made messages (after extraction) 2014-07-02 02:04:42 +00:00
l10n daemon script
92451df32f SVN_SILENT made messages (after extraction) 2014-07-01 02:06:59 +00:00
l10n daemon script
55eada9dfc SVN_SILENT made messages (after extraction) 2014-06-30 02:05:57 +00:00
Albert Vaca
0ed73f6729 Ooops, added missing file that was untracked! 2014-06-29 19:44:30 +02:00
Albert Vaca
c8e24bf07d Increased version number to release 2014-06-29 18:52:18 +02:00
Albert Vaca
f016a06c32 Notify the system when we receive a file, so it gets added to the gallery, etc. 2014-06-29 18:50:50 +02:00
Ahmed I. Khalil
23463afc33 Added Right and Middle Clicks as Menu Items.
I've made them as "never" to be shown on the ActionBar,
until hopefully we could get icons for them.

REVIEW: 119016
2014-06-29 18:37:28 +02:00
Albert Vaca
45fd3f88e8 Added methods for right and middle click, not yet used 2014-06-29 17:58:33 +02:00
Albert Vaca
b54e2ea36e Made scrolling less sensitive
CCMAIL: ahmedibrahimkhali@gmail.com
2014-06-29 17:58:21 +02:00
Albert Vaca
3043ca5d05 Increased the version number to release 2014-06-29 17:17:43 +02:00
Albert Vaca
47368a27fd Trying to fix a "jumpy mouse" issue when scrolling
CCMAIL: ahmedibrahimkhali@gmail.com
2014-06-29 17:17:22 +02:00
Albert Vaca
046a1cb676 Implemented two finger scrolling on Android, by handling the onScroll method
that it is called from the GestureDetector.

REVIEW: 119007
2014-06-29 17:17:22 +02:00
Albert Vaca
43c6742e21 Device names could contain characters not allowed for a dir name, fixed. 2014-06-29 17:17:22 +02:00
Albert Vaca
5b2f863e0e Added padding to text 2014-06-29 17:17:22 +02:00
Albert Vaca
a12e64ca59 Renamed mousepad -> touchpad 2014-06-29 17:17:22 +02:00
Albert Vaca
e99ba38eac Removed unnecesary import 2014-06-29 17:17:22 +02:00
Albert Vaca
0730c52741 Started implementing receiving shared files 2014-06-29 17:17:22 +02:00
l10n daemon script
093b722f00 SVN_SILENT made messages (after extraction) 2014-06-29 01:58:07 +00:00
Albert Vaca
162fc8b8dd Increased version number to release 2014-06-27 17:33:03 +02:00
Antoni Bella Pérez
c648185ffa Added Sony Xperia Z2 human readable name 2014-06-27 17:29:13 +02:00
Albert Vaca
af915b5756 I like the Nexus phones being called only "Nexus" :P 2014-06-27 17:28:50 +02:00
Albert Vaca
dc60968a35 Merge branch 'feature/mousepad' 2014-06-27 17:22:52 +02:00
Albert Vaca
c122a0d2f3 Removed two error dialogs that were not used 2014-06-27 15:55:22 +02:00
Ahmed I. Khalil
ce597f2e51 Android Implementation of MousePad feature
CCMAIL: ahmedibrahimkhali@gmail.com
2014-06-27 15:48:11 +02:00
Albert Vaca
7ae687ae11 Explicitly capturing plugins exceptions to avoid them being lost magically 2014-06-27 14:47:10 +02:00
Albert Vaca
53bb376990 More projet updates 2014-06-27 14:47:10 +02:00
Albert Vaca
cc10538dcb Added more device names 2014-06-27 14:47:10 +02:00
Albert Vaca
9c7aaf2ed9 Merge https://github.com/R4md4c/kdeconnect-android into feature/mousepad 2014-06-27 14:45:05 +02:00
l10n daemon script
0283b0112e SVN_SILENT made messages (after extraction) 2014-06-24 01:52:25 +00:00
l10n daemon script
906fc35abb SVN_SILENT made messages (after extraction) 2014-06-21 01:56:44 +00:00
l10n daemon script
8bc3e69ce6 SVN_SILENT made messages (after extraction) 2014-06-20 02:08:24 +00:00
l10n daemon script
020d2bca2a SVN_SILENT made messages (after extraction) 2014-06-19 01:48:31 +00:00
Albert Vaca
50682935e0 Explicitly capturing plugins exceptions to avoid them being lost magically 2014-06-18 22:53:52 +02:00
Albert Vaca
1da74a6c68 More projet updates 2014-06-18 22:53:11 +02:00
Aleix Pol
aa33f4cd08 Submit .reviewboardrc file
Simplifies reviewboard patches submision
2014-06-18 02:43:41 +02:00
l10n daemon script
0c142928f9 SVN_SILENT made messages (after extraction) 2014-06-16 01:57:09 +00:00
Albert Vaca
24aefebb0e Added more device names 2014-06-14 19:08:25 +02:00
l10n daemon script
7a47bd0b9c SVN_SILENT made messages (after extraction) 2014-06-14 01:52:05 +00:00
l10n daemon script
cbb3787a15 SVN_SILENT made messages (after extraction) 2014-06-12 01:48:42 +00:00
l10n daemon script
61048e5df4 SVN_SILENT made messages (after extraction) 2014-06-11 01:54:17 +00:00
Albert Vaca
8644f7ee15 Paired unreachable devices show an "unpair" button instead of an empty view 2014-06-09 13:25:50 +02:00
Albert Vaca
77f9a30bd3 Fixed bug causing plugins to load for unreachable devices 2014-06-09 13:25:50 +02:00
Albert Vaca
1dbc93ec73 Inform other devices we don't trust them when they talk to us 2014-06-09 13:25:50 +02:00
Albert Vaca
64c5362b1c Updated android build tools 2014-06-09 13:25:50 +02:00
l10n daemon script
0c4ae62061 SVN_SILENT made messages (after extraction) 2014-06-06 02:02:50 +00:00
l10n daemon script
727c845624 SVN_SILENT made messages (after extraction) 2014-05-31 01:52:48 +00:00
l10n daemon script
577d9a5dc8 SVN_SILENT made messages (after extraction) 2014-05-18 01:54:52 +00:00
Albert Vaca
0311163829 Updated version number to release 2014-05-14 01:23:34 +02:00
Albert Vaca
76fd6d1e08 Better exception handling in sendMessage 2014-05-14 01:21:28 +02:00
l10n daemon script
a79e8f83ce SVN_SILENT made messages (after extraction) 2014-05-05 01:59:18 +00:00
Albert Vaca
3f6ec6a2d7 Broadcast device name after changing it 2014-04-12 20:50:19 +02:00
Albert Vaca
726f623eea Project settings: changed from release to debug 2014-04-12 20:44:39 +02:00
l10n daemon script
0db8f7c1d4 SVN_SILENT made messages (after extraction) 2014-03-29 01:59:41 +00:00
Albert Vaca
7a289fbe38 Fixed silly stuff reported by the lint analyzer 2014-03-29 01:47:15 +01:00
Albert Vaca
41bb2ed916 Increased version number to release 2014-03-27 23:19:58 +01:00
Sven Nobis
4598f080a2 Read notifications ticker text from extras Bundle.
Many Apps don't set a useful ticker text. On Android version KitKat or
newer, the title and text of a notification is stored in the extras Bundle.
This patch make use of it, to provide a better ticker text.
2014-03-27 23:18:56 +01:00
l10n daemon script
ecf22e889b SVN_SILENT made messages (after extraction) 2014-03-26 01:55:42 +00:00
l10n daemon script
8598cf5f2e SVN_SILENT made messages (after extraction) 2014-03-24 01:49:14 +00:00
l10n daemon script
1def9cc9ce SVN_SILENT made messages (after extraction) 2014-03-20 01:52:22 +00:00
l10n daemon script
0772a37f4e SVN_SILENT made messages (after extraction) 2014-03-19 04:23:04 +00:00
l10n daemon script
16edb35a0e SVN_SILENT made messages (after extraction) 2014-03-18 01:46:41 +00:00
Albert Vaca
8f9bad7a0f Increased version number to release 2014-03-15 17:47:50 +01:00
Mariusz Fik
4fd96062f6 Fix crash when user tries to unload plugin, which is not supported by platform. 2014-03-15 17:32:16 +01:00
l10n daemon script
a0a9eca060 SVN_SILENT made messages (after extraction) 2014-03-10 01:56:44 +00:00
Albert Vaca
5999ef7c8e Increased version number to release 2014-03-08 00:21:17 +01:00
Albert Vaca
9542597424 Updated project to latest version of Android build tools 2014-03-08 00:21:06 +01:00
Mariusz Fik
11e5563d9a Use hardware buttons to control multimedia player volume.
REVIEW: 116652
2014-03-07 23:27:01 +01:00
Albert Vaca
f31afdb6d0 Increased version number to release 2014-03-07 23:25:42 +01:00
l10n daemon script
27ec7c5bfb SVN_SILENT made messages (after extraction) 2014-03-03 01:48:29 +00:00
l10n daemon script
49360816a3 SVN_SILENT made messages (after extraction) 2014-02-24 01:51:22 +00:00
Albert Vaca
dd364bd562 New icon by Malcer <malcer@gmx.com> 2014-02-19 21:20:15 +01:00
Albert Vaca
809f37a541 More project changes 2014-02-19 17:03:47 +01:00
Albert Vaca
36c3df7a07 Version should be 0.5 instead of 0.6! 2014-02-19 16:33:08 +01:00
l10n daemon script
34aec7120e SVN_SILENT made messages (after extraction) 2014-02-19 02:37:41 +00:00
l10n daemon script
0a494b002d SVN_SILENT made messages (after extraction) 2014-02-18 01:46:47 +00:00
l10n daemon script
7298247dfc SVN_SILENT made messages (after extraction) 2014-02-17 02:14:15 +00:00
l10n daemon script
72a97d9e1c SVN_SILENT made messages (after extraction) 2014-02-16 03:52:01 +00:00
l10n daemon script
7cc80baf16 SVN_SILENT made messages (after extraction) 2014-02-15 01:46:20 +00:00
Albert Vaca
0600b1ee2e Oops, duplicate library 2014-02-14 20:11:22 +01:00
Albert Vaca
2f0059938d Increased version number to tag and release 2014-02-14 17:08:30 +01:00
Albert Vaca
a023316609 Renamed sftp plugin to "filesystem browser" 2014-02-14 17:08:30 +01:00
Albert Vaca
d94ed56c4e oops 2014-02-14 17:08:30 +01:00
Samoilenko Yuri
15a11cce02 Publick key auth for sftp plugin 2014-02-14 17:08:07 +01:00
Albert Vaca
36530b9580 Prefer IPv4 over IPv6 2014-02-14 17:07:42 +01:00
Samoilenko Yuri
f54acd7308 fix 2014-02-14 17:07:42 +01:00
Samoilenko Yuri
cfe9ccdc31 WORKAROUND new gradle android plugin bug 2014-02-14 17:07:42 +01:00
Samoilenko Yuri
52c7581f42 moved to updated gradle and Android Studio 2014-02-14 17:07:12 +01:00
Albert Vaca
a57dc71ad1 Libs are now included from Maven repo 2014-02-14 17:05:31 +01:00
Samoilenko Yuri
77d06cfe77 sftp implementations splits from sftpplugin 2014-02-14 17:04:36 +01:00
Samoilenko Yuri
13a7c1e5fe first draft SFTP implementation 2014-02-14 17:04:30 +01:00
l10n daemon script
5ccf215a54 SVN_SILENT made messages (after extraction) 2014-02-06 01:38:48 +00:00
l10n daemon script
fddf3c64b5 SVN_SILENT made messages (after extraction) 2014-02-01 01:41:08 +00:00
l10n daemon script
fb02b9bdf1 SVN_SILENT made messages (after extraction) 2014-01-29 01:40:11 +00:00
l10n daemon script
8453156d16 SVN_SILENT made messages (after extraction) 2014-01-23 01:37:44 +00:00
Albert Vaca
9bbe2908ed We will only sync actual notifications from now on
BUG: 325668
2014-01-10 19:29:50 +01:00
Albert Vaca
fb843f8789 Notifications with FLAG_FOREGROUND_SERVICE are not shown anymore
Actually, they are not 'notifications'.
2014-01-10 18:00:26 +01:00
Albert Vaca
c85619dfd2 Phone number shown next to contact name for calls/sms
BUG: 328197
2014-01-10 17:59:50 +01:00
Albert Vaca
de9f78adf3 Gradle upgrade to make Android Studio 4.2 happy 2014-01-10 17:07:00 +01:00
Samoilenko Yuri
9021ab9454 Partial impl of clipboard sync in early Android
Syncronization works only in one direction - incoming
2014-01-08 02:17:37 +04:00
Samoilenko Yuri
10210b3972 Fixed Ping notification on old phones
On old phones(LG P698) notification does not shown ultil all
fields are filles correctly: setContentIntent.
Added pendingIntent to notification as describved into official documentation for Notifications, to
fit support library requirements.
2014-01-08 01:20:24 +04:00
l10n daemon script
f83b9cbe0c SVN_SILENT made messages (after extraction) 2013-12-29 01:40:33 +00:00
l10n daemon script
7d821e7c88 SVN_SILENT made messages (after extraction) 2013-12-10 01:38:14 +00:00
l10n daemon script
02070343fe SVN_SILENT made messages (after extraction) 2013-11-28 01:42:19 +00:00
l10n daemon script
416a000dd3 SVN_SILENT made messages (after extraction) 2013-11-25 01:41:50 +00:00
Albert Vaca
a15cad9ec5 Increased version number to release 2013-11-24 17:49:15 +01:00
l10n daemon script
e98100f5f0 SVN_SILENT made messages (after extraction) 2013-11-24 01:37:28 +00:00
Albert Vaca
d19e7d2052 Missing permission (how could this work before?) 2013-11-23 01:49:34 +01:00
Albert Vaca
4e02c674f8 Still trying to fix the concurrency exception
I hope this doesn't cause infinite recursion.
2013-11-23 01:49:34 +01:00
Albert Vaca
6aa49757fc Fixed bug retrieving incorrect file sizes for file transfers
For some reason querying the content provider returns a different size than
using File.length()
2013-11-23 01:49:33 +01:00
Albert Vaca
3c10312d15 More Android Studio nonsensical project changes 2013-11-23 01:49:33 +01:00
l10n daemon script
26e3363b30 SVN_SILENT made messages (after extraction) 2013-11-19 01:37:14 +00:00
l10n daemon script
4033233ec1 SVN_SILENT made messages (after extraction) 2013-11-18 01:45:52 +00:00
Albert Vaca
34e53576e1 Trying to fix a concurrency exception 2013-11-15 15:31:38 +01:00
Albert Vaca
a15d76c588 Android Studio keeps changing the project files... 2013-11-14 18:41:35 +01:00
Albert Vaca
1acea73383 The battery percentage for low battery level is now signaled by the device
BatteryPlugin is now subscribed to the BATTERY_LOW event

REVIEW: 113838
2013-11-14 18:24:44 +01:00
Albert Vaca
6c8fdb0be7 Increased version number to release 2013-11-14 18:20:57 +01:00
l10n daemon script
749170a29c SVN_SILENT made messages (after extraction) 2013-11-14 01:36:45 +00:00
l10n daemon script
81b08cf89f SVN_SILENT made messages (after extraction) 2013-11-11 01:40:35 +00:00
Albert Vaca
786ea7658f Fixed bugs in NotificationsPlugin
Already existing notifications were not retrieved
Notifications without tickerText were crashing the plugin
2013-11-06 21:15:49 +01:00
Albert Vaca
996516ec88 Fixed a bug while pairing
Timeout was being fired after the pairing was done
Added lots of Log.e for debugging, now commented
2013-11-06 21:13:37 +01:00
Albert Vaca
a36e4a0c88 Added deviceType to the identity packages
This will tell if we are a phone, tablet, laptop or desktop
2013-11-06 19:06:18 +01:00
l10n daemon script
e08de81c73 SVN_SILENT made messages (after extraction) 2013-11-05 01:33:50 +00:00
Albert Vaca
30d2665b12 Upgraded project to new Android SDK
Now using gradle magic imports instead of jar files for libraries
2013-11-02 17:46:20 +01:00
l10n daemon script
c14946e9e4 SVN_SILENT made messages (after extraction) 2013-11-02 01:35:34 +00:00
l10n daemon script
1aab14fd50 SVN_SILENT made messages (after extraction) 2013-10-31 01:37:52 +00:00
l10n daemon script
549fc01c1d SVN_SILENT made messages (after extraction) 2013-10-30 01:42:44 +00:00
Albert Vaca
d59de8a0b6 Increased version to release 2013-10-30 01:46:00 +01:00
Mauro Panzeri
8823277e74 Follow the android guidelines for icons/drawables and their names.
Guidelines: http://goo.gl/E70Ez

REVIEW: 113200
2013-10-30 01:45:27 +01:00
Albert Vaca
30cf27e835 NetworkPackage.encrypt now returns a new package instead of changing itself
This was causing problems with plugins that keep a reference to the
packages they have already sent, like the telephony plugin, because the
package became encrypted (and thus inaccessible) after sending it.

BUG: 326275
2013-10-30 01:37:55 +01:00
Mauro Panzeri
9dbe1ec9cf Added a settings activity to change the publicly visible device name
When changed the new name will be used instead of the one from
HumanDeviceNames

REVIEW: 113204
BUG: 325061
2013-10-30 01:37:47 +01:00
Albert Vaca
0595662efc Spotify doesn't support seeking, disable mpris seek buttons for it 2013-10-30 01:31:21 +01:00
Albert Vaca
6609ae665c Fixed a crash trying to disconnect not-yet-active sessions. 2013-10-30 01:31:20 +01:00
Maksim Krylosov
2f26fa5188 Added rewind and fast-forward buttons to MPRIS controls 2013-10-30 01:31:20 +01:00
Albert Vaca
6471bafdd9 Fixed important bug trying to load device settings before deviceId was set
(Also improved code style and fixed minor TODOs)
2013-10-30 01:31:20 +01:00
Albert Vaca
0620810e4b Upgraded to build tools 18.1.1 and gradle 1.8 2013-10-30 01:31:20 +01:00
Albert Vaca
db9209765d Android Studio is getting confused if project name != directory name 2013-10-29 17:47:07 +01:00
Mauro Panzeri
e459d792d0 "Share to" now retrieves filename for file:// uris too
Querying the ContentProvider only works for content://... uris but when you
share a file from a file-manager, the uri you get has the form file://... and
ContentProvider returns a null cursor.

BUG: 325684
REVIEW: 113202
2013-10-29 17:47:07 +01:00
Albert Vaca
31fcc3e184 Project refactor because Android Studio was not able to open it
For some reason it was not detecting the directory as an Android project
Android Build Tools version increased from 18.0.1 to 18.1
2013-10-29 17:47:07 +01:00
l10n daemon script
544b5e8c87 SVN_SILENT made messages (after extraction) 2013-10-25 01:35:28 +00:00
l10n daemon script
7819ae1ca2 SVN_SILENT made messages (after extraction) 2013-10-18 01:44:45 +00:00
l10n daemon script
98885d6c35 SVN_SILENT made messages (after extraction) 2013-10-17 01:34:48 +00:00
l10n daemon script
485e646f6f SVN_SILENT made messages (after extraction) 2013-10-14 01:51:48 +00:00
l10n daemon script
dfece0a414 SVN_SILENT made messages (after extraction) 2013-10-11 01:53:05 +00:00
l10n daemon script
bbaf40165c SVN_SILENT made messages (after extraction) 2013-10-07 01:47:28 +00:00
Albert Vaca
b221fa04bf Increased version number for release 2013-10-05 17:26:51 +02:00
Albert Vaca
07bf1e9ebc Made sendPackage sync, so mutex hack in Device is no longer needed 2013-10-05 17:26:51 +02:00
Albert Vaca
a07c9a9fb7 Fixed warnings about cursors not closed 2013-10-05 17:26:51 +02:00
l10n daemon script
40a9171985 SVN_SILENT made messages (after extraction) 2013-10-04 01:52:34 +00:00
Albert Vaca
69197fd657 Increased version for release. 2013-10-03 15:52:10 +02:00
Albert Vaca
55e3861c5c Fixed a crash when pressing back from remote controls in action bar
Device activity isn't receiving the device id in that case.
2013-10-03 15:52:10 +02:00
Albert Vaca
5d00e6e168 Added a mutex to avoid files being sent all at the same time
It's a bit hackish but works.
2013-10-03 15:52:09 +02:00
l10n daemon script
967635a2f4 SVN_SILENT made messages (after extraction) 2013-10-03 01:46:02 +00:00
Albert Astals Cid
58b31fa032 Transition to the StaticMessages.sh thing
Let's see what next scripty run says
CCMAIL: albertvaka@gmail.com
2013-10-01 21:52:09 +02:00
123 changed files with 4817 additions and 624 deletions

9
.gitignore vendored
View File

@@ -1,4 +1,7 @@
local.properties
.gradle
.idea
out
.gradle/
.idea/
out/
build/
target/
classes/

3
.reviewboardrc Normal file
View File

@@ -0,0 +1,3 @@
REVIEWBOARD_URL = "https://git.reviewboard.kde.org"
REPOSITORY = 'git://anongit.kde.org/kdeconnect-android'
TARGET_GROUPS = 'kdeconnect'

View File

@@ -1 +0,0 @@
build/

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="Debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebug" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":KdeConnect" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android 4.3 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="android-support-v4" level="project" />
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
<orderEntry type="library" exported="" name="ComAndroidSupportAppcompatV71800.aar" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.6.6" level="project" />
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />
</component>
</module>

View File

@@ -1,26 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.5.+'
}
}
apply plugin: 'android'
dependencies {
compile "com.android.support:appcompat-v7:18.0.+"
compile files('libs/android-support-v4.jar')
compile files('libs/mina-core-2.0.7.jar')
compile files('libs/slf4j-api-1.6.6.jar')
}
android {
compileSdkVersion 18
buildToolsVersion "18.0.1"
defaultConfig {
minSdkVersion 9
targetSdkVersion 18
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,148 +0,0 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.List.ButtonItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
public class DeviceActivity extends ActionBarActivity {
private String deviceId;
private Device device;
private Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
@Override
public void onPluginsChanged(final Device device) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//Errors list
final HashMap<String, Plugin> failedPlugins = device.getFailedPlugins();
final String[] ids = failedPlugins.keySet().toArray(new String[failedPlugins.size()]);
String[] names = new String[failedPlugins.size()];
for(int i = 0; i < ids.length; i++) {
Plugin p = failedPlugins.get(ids[i]);
names[i] = p.getDisplayName();
}
ListView errorList = (ListView)findViewById(R.id.errors_list);
if (!failedPlugins.isEmpty() && errorList.getHeaderViewsCount() == 0) {
TextView header = new TextView(DeviceActivity.this);
header.setPadding(0,24,0,0);
header.setText(getResources().getString(R.string.plugins_failed_to_load));
errorList.addHeaderView(header);
}
errorList.setAdapter(new ArrayAdapter<String>(DeviceActivity.this, android.R.layout.simple_list_item_1, names));
errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to substract one
p.getErrorDialog(DeviceActivity.this).show();
}
});
//Buttons list
ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
final Collection<Plugin> plugins = device.getLoadedPlugins().values();
for (Plugin p : plugins) {
Button b = p.getInterfaceButton(DeviceActivity.this);
if (b != null) {
items.add(new SectionItem(p.getDisplayName()));
items.add(new ButtonItem(b));
}
}
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setDisplayHomeAsUpEnabled(true);
deviceId = getIntent().getStringExtra("deviceId");
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
setTitle(device.getName());
device.addPluginsChangedListener(pluginsChangedListener);
pluginsChangedListener.onPluginsChanged(device);
}
});
}
@Override
protected void onDestroy() {
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
device.removePluginsChangedListener(pluginsChangedListener);
}
});
super.onDestroy();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
if (device.isPaired()) {
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
intent.putExtra("deviceId", deviceId);
startActivity(intent);
return true;
}
});
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
device.unpair();
finish();
return true;
}
});
return true;
} else {
return false;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,21 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_refresh"
android:icon="@drawable/navigation_refresh"
android:orderInCategory="200"
kdeconnect:showAsAction="always"
android:title="@string/reconnect"
/>
<item
android:id="@+id/menu_progress"
android:icon="@drawable/navigation_refresh"
android:orderInCategory="200"
android:visible="false"
kdeconnect:showAsAction="always"
kdeconnect:actionViewClass="android.widget.ProgressBar"
/>
</menu>

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
mkdir locale
a2po export --android KdeConnect/src/main/res/ --gettext locale
mv locale/template.pot $podir/kdeconnect-android.pot
rm -rf locale

21
StaticMessages.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# The name of catalog we create (without the.pot extension), sourced from the scripty scripts
FILENAME="kdeconnect-android"
function export_pot_file # First parameter will be the path of the pot file we have to create, includes $FILENAME
{
potfile=$1
mkdir outdir
a2po export --android src/main/res/ --gettext outdir
mv outdir/template.pot $potfile
rm -rf outdir
}
function import_po_files # First parameter will be a path that will contain several .po files with the format LANG.po
{
podir=$1
a2po import --android src/main/res/ --gettext $podir
}

View File

@@ -1,2 +1,46 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 19
buildToolsVersion '20.0.0'
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
packagingOptions {
exclude "META-INF/DEPENDENCIES"
exclude "META-INF/NOTICE"
exclude "META-INF/LICENSE"
exclude "META-INF/LICENSE.txt"
exclude "META-INF/NOTICE.txt"
}
lintOptions {
abortOnError false
}
productFlavors {
}
buildTypes {
}
}
dependencies {
repositories {
mavenCentral()
}
compile 'com.android.support:support-v4:19.1.+'
compile 'com.android.support:appcompat-v7:19.1.+'
compile 'org.apache.mina:mina-core:2.0.+'
compile 'org.bouncycastle:bcprov-jdk16:1.45'
compile 'org.apache.sshd:sshd-core:0.8.0'
compile 'tomcat:tomcat-apr:5.5.+'
//compile fileTree(dir: 'libs', include: '*.jar')
}

View File

@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Mon Jun 09 09:51:48 CEST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip

1067
icon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,13 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="sshd-core-0.8.0" level="project" />
<orderEntry type="library" exported="" name="tomcat-apr-5.5.15" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.6.6" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-19.1.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
<orderEntry type="library" exported="" name="bcprov-jdk16-1.45" level="project" />
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />
</component>
</module>

View File

@@ -1 +0,0 @@
include ':KdeConnect'

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.kde.kdeconnect_tp"
android:versionCode="5"
android:versionName="0.3.0">
android:versionCode="720"
android:versionName="0.7.2">
<uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="18" />
android:targetSdkVersion="19" />
<supports-screens
android:smallScreens="true"
@@ -18,13 +18,15 @@
<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.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.RECEIVE_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"/>
<application
android:allowBackup="true"
@@ -44,11 +46,19 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.MainSettingsActivity"
android:label="@string/settings"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.DeviceActivity"
@@ -57,8 +67,7 @@
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
</activity>
<activity
android:theme="@style/Theme.AppCompat"
@@ -68,7 +77,6 @@
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
@@ -117,6 +125,17 @@
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
android:label="@string/remote_control"
android:screenOrientation="fullSensor"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.ShareToReceiver"

View File

@@ -1,23 +1,17 @@
package org.kde.kdeconnect.Backends;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import org.kde.kdeconnect.NetworkPackage;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
public abstract class BaseLink {
private BaseLinkProvider linkProvider;
private String deviceId;
private ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
private final BaseLinkProvider linkProvider;
private final String deviceId;
private final ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
protected PrivateKey privateKey;
protected BaseLink(String deviceId, BaseLinkProvider linkProvider) {
@@ -56,7 +50,7 @@ public abstract class BaseLink {
}
}
//TO OVERRIDE
//TO OVERRIDE, should be sync
public abstract boolean sendPackage(NetworkPackage np);
public abstract boolean sendPackageEncrypted(NetworkPackage np, PublicKey key);

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList;
public abstract class BaseLinkProvider {
private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link);

View File

@@ -1,43 +1,28 @@
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
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.IoHandlerAdapter;
import org.apache.mina.core.future.WriteFuture;
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.NioSocketConnector;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
public class LanLink extends BaseLink {
private IoSession session = null;
public void disconnect() {
Log.i("LanLink", "Disconnect: "+session.getRemoteAddress().toString());
if (session == null) return;
//Log.i("LanLink", "Disconnect: "+session.getRemoteAddress().toString());
session.close(true);
}
@@ -46,10 +31,12 @@ public class LanLink extends BaseLink {
this.session = session;
}
private JSONObject sendPayload(final InputStream stream) {
private Thread sendPayload(NetworkPackage np) {
try {
final InputStream stream = np.getPayload();
ServerSocket candidateServer = null;
boolean success = false;
int tcpPort = 1739;
@@ -61,14 +48,17 @@ public class LanLink extends BaseLink {
} catch(Exception e) {
Log.e("LanLink", "Exception openning serversocket: "+e);
tcpPort++;
if (tcpPort >= 1764) return new JSONObject();
if (tcpPort >= 1764) {
Log.e("LanLink", "No more ports available");
return null;
}
}
}
JSONObject payloadTransferInfo = new JSONObject();
payloadTransferInfo.put("port", tcpPort);
final ServerSocket server = candidateServer;
new Thread(new Runnable() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//TODO: Timeout when waiting for a connection and close the socket
@@ -93,9 +83,12 @@ public class LanLink extends BaseLink {
try { server.close(); } catch(Exception e) { }
}
}
}).start();
});
thread.start();
return payloadTransferInfo;
np.setPayloadTransferInfo(payloadTransferInfo);
return thread;
} catch(Exception e) {
@@ -106,23 +99,41 @@ public class LanLink extends BaseLink {
}
}
//Blocking, do not call from main thread
@Override
public boolean sendPackage(final NetworkPackage np) {
if (session == null) {
Log.e("LanLink", "sendPackage failed: not yet connected");
return false;
}
if (np.hasPayload()) {
JSONObject transferInfo = sendPayload(np.getPayload());
np.setPayloadTransferInfo(transferInfo);
try {
Thread thread = null;
if (np.hasPayload()) {
thread = sendPayload(np);
if (thread == null) return false;
}
WriteFuture future = session.write(np.serialize());
future.awaitUninterruptibly();
if (!future.isWritten()) return false;
if (thread != null) {
thread.join(); //Wait for thread to finish
}
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLink", "sendPackage exception");
return false;
}
session.write(np.serialize());
return true;
}
//Blocking, do not call from main thread
@Override
public boolean sendPackageEncrypted(NetworkPackage np, PublicKey key) {
@@ -133,19 +144,25 @@ public class LanLink extends BaseLink {
try {
Thread thread = null;
if (np.hasPayload()) {
JSONObject transferInfo = sendPayload(np.getPayload());
np.setPayloadTransferInfo(transferInfo);
thread = sendPayload(np);
if (thread == null) return false;
}
np.encrypt(key);
np = np.encrypt(key);
WriteFuture future = session.write(np.serialize());
if (!future.await().isWritten()) return false;
session.write(np.serialize());
if (thread != null) {
thread.join(); //Wait for thread to finish
}
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLink", "Encryption exception");
Log.e("LanLink", "sendPackageEncrypted exception");
return false;
}

View File

@@ -3,6 +3,7 @@ package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.support.v4.util.LongSparseArray;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFuture;
@@ -30,9 +31,9 @@ public class LanLinkProvider extends BaseLinkProvider {
private final static int port = 1714;
private Context context;
private HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>();
private HashMap<Long, LanLink> nioSessions = new HashMap<Long, LanLink>();
private final Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>();
private final LongSparseArray<LanLink> nioSessions = new LongSparseArray<LanLink>();
private NioSocketAcceptor tcpAcceptor = null;
private NioDatagramAcceptor udpAcceptor = null;
@@ -41,8 +42,9 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override
public void sessionClosed(IoSession session) throws Exception {
LanLink brokenLink = nioSessions.remove(session.getId());
LanLink brokenLink = nioSessions.get(session.getId());
if (brokenLink != null) {
nioSessions.remove(session.getId());
connectionLost(brokenLink);
brokenLink.disconnect();
String deviceId = brokenLink.getDeviceId();
@@ -58,11 +60,15 @@ public class LanLinkProvider extends BaseLinkProvider {
super.messageReceived(session, message);
//Log.e("LanLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString());
//Log.e("LanLinkProvider","Received:"+message);
String theMessage = (String) message;
NetworkPackage np = NetworkPackage.unserialize(theMessage);
if (theMessage.isEmpty()) {
Log.e("LanLinkProvider","Empty package received");
return;
}
LanLink prevLink = nioSessions.get(session.getId());
NetworkPackage np = NetworkPackage.unserialize(theMessage);
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
@@ -77,6 +83,7 @@ public class LanLinkProvider extends BaseLinkProvider {
nioSessions.put(session.getId(),link);
addLink(np, link);
} else {
LanLink prevLink = nioSessions.get(session.getId());
if (prevLink == null) {
Log.e("LanLinkProvider","2 Expecting an identity package");
} else {
@@ -87,7 +94,7 @@ public class LanLinkProvider extends BaseLinkProvider {
}
};
private IoHandler udpHandler = new IoHandlerAdapter() {
private final IoHandler udpHandler = new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession udpSession, Object message) throws Exception {
super.messageReceived(udpSession, message);
@@ -128,17 +135,23 @@ public class LanLinkProvider extends BaseLinkProvider {
future.addListener(new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture ioFuture) {
IoSession session = ioFuture.getSession();
final IoSession session = ioFuture.getSession();
final LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this);
Log.i("LanLinkProvider", "Connection successful: " + session.isConnected());
LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this);
new Thread(new Runnable() {
@Override
public void run() {
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
link.sendPackage(np2);
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
link.sendPackage(np2);
nioSessions.put(session.getId(), link);
addLink(identityPackage, link);
}
}).start();
nioSessions.put(session.getId(), link);
addLink(identityPackage, link);
}
});

View File

@@ -26,7 +26,7 @@ public class LoopbackLink extends BaseLink {
@Override
public boolean sendPackageEncrypted(NetworkPackage in, PublicKey key) {
try {
in.encrypt(key);
in = in.encrypt(key);
String s = in.serialize();
NetworkPackage out= NetworkPackage.unserialize(s);
out.decrypt(privateKey);

View File

@@ -7,7 +7,7 @@ import org.kde.kdeconnect.NetworkPackage;
public class LoopbackLinkProvider extends BaseLinkProvider {
private Context context;
private final Context context;
public LoopbackLinkProvider(Context context) {
this.context = context;

View File

@@ -14,7 +14,7 @@ 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.Backends.LoopbackBackend.LoopbackLinkProvider;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -26,11 +26,11 @@ import java.util.concurrent.locks.ReentrantLock;
public class BackgroundService extends Service {
private ArrayList<BaseLinkProvider> linkProviders = new ArrayList<BaseLinkProvider>();
private final ArrayList<BaseLinkProvider> linkProviders = new ArrayList<BaseLinkProvider>();
private HashMap<String, Device> devices = new HashMap<String, Device>();
private final HashMap<String, Device> devices = new HashMap<String, Device>();
private Device.PairingCallback devicePairingCallback = new Device.PairingCallback() {
private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() {
@Override
public void incomingRequest() {
if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
@@ -50,9 +50,11 @@ public class BackgroundService extends Service {
};
private void loadRememberedDevicesFromSettings() {
//Log.e("BackgroundService", "Loading remembered trusted devices");
SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
Set<String> trustedDevices = preferences.getAll().keySet();
for(String deviceId : trustedDevices) {
//Log.e("BackgroundService", "Loading device "+deviceId);
if (preferences.getBoolean(deviceId, false)) {
Device device = new Device(this, deviceId);
devices.put(deviceId,device);
@@ -61,7 +63,7 @@ public class BackgroundService extends Service {
}
}
public void registerLinkProviders() {
private void registerLinkProviders() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
@@ -79,7 +81,7 @@ public class BackgroundService extends Service {
return devices.get(id);
}
private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseLink link) {
@@ -94,7 +96,6 @@ public class BackgroundService extends Service {
device.addLink(identityPackage, link);
} else {
Log.i("BackgroundService", "addLink,unknown device: " + deviceId);
String name = identityPackage.getString("deviceName");
device = new Device(BackgroundService.this, identityPackage, link);
devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback);
@@ -110,6 +111,7 @@ public class BackgroundService extends Service {
if (d != null) {
d.removeLink(link);
if (!d.isReachable() && !d.isPaired()) {
//Log.e("onConnectionLost","Removing connection device because it was not paired");
devices.remove(link.getDeviceId());
d.removePairingCallback(devicePairingCallback);
}
@@ -179,6 +181,7 @@ public class BackgroundService extends Service {
Log.i("BackgroundService","Service not started yet, initializing...");
initializeRsaKeys();
MainSettingsActivity.initializeDeviceName(this);
loadRememberedDevicesFromSettings();
registerLinkProviders();
@@ -262,9 +265,9 @@ public class BackgroundService extends Service {
void onServiceStart(BackgroundService service);
}
private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
private final static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
private static final Lock mutex = new ReentrantLock(true);
private final static Lock mutex = new ReentrantLock(true);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {

View File

@@ -7,7 +7,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
@@ -36,11 +35,11 @@ import java.util.TimerTask;
public class Device implements BaseLink.PackageReceiver {
private Context context;
private final Context context;
private String deviceId;
private String name;
private PublicKey publicKey;
private final String deviceId;
private final String name;
public PublicKey publicKey;
private int notificationId;
private int protocolVersion;
@@ -62,11 +61,11 @@ public class Device implements BaseLink.PackageReceiver {
private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>();
private Timer pairingTimer;
private ArrayList<BaseLink> links = new ArrayList<BaseLink>();
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
private final ArrayList<BaseLink> links = new ArrayList<BaseLink>();
private final HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private final HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
SharedPreferences settings;
private final SharedPreferences settings;
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device(Context context, String deviceId) {
@@ -93,17 +92,18 @@ public class Device implements BaseLink.PackageReceiver {
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
Device(Context context, NetworkPackage np, BaseLink dl) {
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
//Log.e("Device","Constructor B");
this.context = context;
this.deviceId = np.getString("deviceId");
this.name = np.getString("deviceName");
this.name = np.getString("deviceName", "unidentified device");
this.protocolVersion = np.getInt("protocolVersion");
this.pairStatus = PairStatus.NotPaired;
this.publicKey = null;
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
addLink(np, dl);
}
@@ -151,15 +151,21 @@ public class Device implements BaseLink.PackageReceiver {
Resources res = context.getResources();
if (pairStatus == PairStatus.Paired) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_paired));
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_paired));
}
return;
}
if (pairStatus == PairStatus.Requested) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_requested));
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_requested));
}
return;
}
if (!isReachable()) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_not_reachable));
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_not_reachable));
}
return;
}
@@ -169,6 +175,7 @@ public class Device implements BaseLink.PackageReceiver {
@Override
public void sendSuccessful() {
if (pairingTimer != null) pairingTimer.cancel();
pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() {
@Override
@@ -176,9 +183,10 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_timed_out));
}
Log.e("Device","Unpairing (timeout A)");
pairStatus = PairStatus.NotPaired;
}
}, 20*1000);
}, 30*1000); //Time to wait for the other to accept
pairStatus = PairStatus.Requested;
}
@@ -187,6 +195,7 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
}
Log.e("Device","Unpairing (sendFailed A)");
pairStatus = PairStatus.NotPaired;
}
@@ -200,8 +209,7 @@ public class Device implements BaseLink.PackageReceiver {
public void unpair() {
if (!isPaired()) return;
//Log.e("Device","Unpairing (unpair)");
pairStatus = PairStatus.NotPaired;
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
@@ -217,44 +225,70 @@ public class Device implements BaseLink.PackageReceiver {
}
public void acceptPairing() {
private void pairingDone() {
Log.i("Device","Accepted pairing");
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np); //TODO: Set a callback
if (pairingTimer != null) pairingTimer.cancel();
pairStatus = PairStatus.Paired;
//Store as trusted device
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).commit();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName());
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey);
editor.commit();
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
for (PairingCallback cb : pairingCallback) {
cb.pairingSuccessful();
}
}
public void acceptPairing() {
Log.i("Device","Accepted pair request started by the other device");
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageFinishedCallback() {
@Override
public void sendSuccessful() {
pairingDone();
}
@Override
public void sendFailed() {
Log.e("Device","Unpairing (sendFailed B)");
pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_not_reachable));
}
}
});
}
public void rejectPairing() {
Log.i("Device","Rejected pairing");
Log.i("Device","Rejected pair request started by the other device");
//Log.e("Device","Unpairing (rejectPairing)");
pairStatus = PairStatus.NotPaired;
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));
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
}
}
@@ -321,9 +355,12 @@ public class Device implements BaseLink.PackageReceiver {
if (wantsPair == isPaired()) {
if (pairStatus == PairStatus.Requested) {
//Log.e("Device","Unpairing (pair rejected)");
pairStatus = PairStatus.NotPaired;
pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
if (pairingTimer != null) pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
}
return;
}
@@ -338,7 +375,9 @@ public class Device implements BaseLink.PackageReceiver {
} catch(Exception e) {
e.printStackTrace();
Log.e("Device","Pairing exception: Received incorrect key");
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_invalid_key));
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_invalid_key));
}
return;
}
@@ -346,23 +385,9 @@ public class Device implements BaseLink.PackageReceiver {
Log.i("Pairing","Pair answer");
pairStatus = PairStatus.Paired;
pairingTimer.cancel();
if (pairingTimer != null) pairingTimer.cancel();
//Store as trusted device
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).commit();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName());
editor.putString("publicKey", encodedPublicKey);
editor.commit();
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
pairingDone();
} else {
@@ -389,15 +414,17 @@ public class Device implements BaseLink.PackageReceiver {
notificationId = (int)System.currentTimeMillis();
notificationManager.notify(notificationId, noti);
if (pairingTimer != null) pairingTimer.cancel();
pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() {
@Override
public void run() {
Log.e("Device","Unpairing (timeout B)");
pairStatus = PairStatus.NotPaired;
notificationManager.cancel(notificationId);
}
}, 19*1000); //Time to show notification
}, 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();
@@ -407,26 +434,35 @@ public class Device implements BaseLink.PackageReceiver {
if (pairStatus == PairStatus.Requested) {
pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
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).commit();
reloadPluginsFromSettings();
}
//Log.e("Device","Unpairing (unpair request)");
pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) cb.unpaired();
}
} else if (!isPaired()) {
//TODO: Notify the other side that we don't trust them
unpair();
Log.e("onPackageReceived","Device not paired, ignoring package!");
} else {
for (Plugin plugin : plugins.values()) {
plugin.onPackageReceived(np);
try {
plugin.onPackageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived()");
}
}
}
@@ -441,33 +477,50 @@ public class Device implements BaseLink.PackageReceiver {
sendPackage(np,null);
}
//Async
public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) {
final Exception backtrace = new Exception();
new Thread(new Runnable() {
@Override
public void run() {
//Log.e("sendPackage", "Sending package...");
//Log.e("sendPackage", np.serialize());
boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
//Log.e("sendPackage", "Sending...");
//We need a copy to avoid concurrent modification exception if the original list changes
ArrayList<BaseLink> mLinks = new ArrayList<BaseLink>(links);
for(BaseLink link : links) {
boolean success;
if (useEncryption) {
success = link.sendPackageEncrypted(np, publicKey);
} else {
success = link.sendPackage(np);
}
if (success) {
//Log.e("sendPackage", "Sent");
if (callback != null) callback.sendSuccessful();
return;
boolean success = false;
try {
for (BaseLink link : mLinks) {
if (useEncryption) {
success = link.sendPackageEncrypted(np, publicKey);
} else {
success = link.sendPackage(np);
}
if (success) break;
}
} catch(Exception e) {
e.printStackTrace();
Log.e("sendPackage","Error while sending package");
success = false;
}
if (callback != null) callback.sendFailed();
Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)");
if (success) {
// Log.e("sendPackage","Package sent");
} else {
backtrace.printStackTrace();
Log.e("sendPackage","Error: Package could not be sent ("+mLinks.size()+" links available)");
}
if (callback != null) {
if (success) callback.sendSuccessful();
else callback.sendFailed();
}
}
}).start();
@@ -486,7 +539,7 @@ public class Device implements BaseLink.PackageReceiver {
return plugins.get(name);
}
private void addPlugin(final String name) {
private synchronized void addPlugin(final String name) {
Plugin existing = plugins.get(name);
if (existing != null) {
Log.w("addPlugin","plugin already present:" + name);
@@ -532,7 +585,7 @@ public class Device implements BaseLink.PackageReceiver {
}
private boolean removePlugin(String name) {
private synchronized boolean removePlugin(String name) {
Plugin plugin = plugins.remove(name);
Plugin failedPlugin = failedPlugins.remove(name);
@@ -564,7 +617,7 @@ public class Device implements BaseLink.PackageReceiver {
public void setPluginEnabled(String pluginName, boolean value) {
settings.edit().putBoolean(pluginName,value).commit();
if (value) addPlugin(pluginName);
if (value && isPaired() && isReachable()) addPlugin(pluginName);
else removePlugin(pluginName);
}
@@ -610,7 +663,7 @@ public class Device implements BaseLink.PackageReceiver {
void onPluginsChanged(Device device);
}
private ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
private final ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);

View File

@@ -13,7 +13,7 @@ public class AppsHelper {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo( packageName, 0);
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
return pm.getApplicationLabel(ai).toString();

View File

@@ -3,9 +3,7 @@ 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.Log;
public class ContactsHelper {
@@ -31,14 +29,17 @@ public class ContactsHelper {
// Take the first match only
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME);
int nameIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
if (nameIndex != -1) {
String name = cursor.getString(nameIndex);
//Log.e("PhoneNumberLookup", "success: " + name);
return name;
cursor.close();
return name + " (" + number + ")";
}
}
if (cursor != null) cursor.close();
return number;
}

View File

@@ -1,15 +1,17 @@
package org.kde.kdeconnect;
package org.kde.kdeconnect.Helpers;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import java.util.HashMap;
public class HumanDeviceNames {
public class DeviceHelper {
//from https://github.com/meetup/android-device-names
//Converted to java using:
//cat android_models.properties | awk -F'=' '{sub(/ *$/, "", $1)} sub(/^ */, "", $2) { if ($2 != "") print "humanReadableNames.put(\""$1"\",\"" $2 "\");"}'
private static HashMap<String,String> humanReadableNames = new HashMap<String,String>();
private final static HashMap<String,String> humanReadableNames = new HashMap<String,String>();
static {
humanReadableNames.put("5860E","Coolpad Quattro 4G");
humanReadableNames.put("ADR6300","HTC Droid Incredible");
@@ -22,6 +24,9 @@ public class HumanDeviceNames {
humanReadableNames.put("C5155","Kyocera Rise");
humanReadableNames.put("C5170","Kyocera Hydro");
humanReadableNames.put("C6603","Sony Xperia Z");
humanReadableNames.put("C6606","Sony Xperia Z");
humanReadableNames.put("C6903","Sony Xperia Z1");
humanReadableNames.put("D6503","Sony Xperia Z2");
humanReadableNames.put("Desire_HD","HTC Desire HD");
humanReadableNames.put("DROID2_GLOBAL","Motorola Droid 2 Global");
humanReadableNames.put("DROID2","Motorola Droid 2");
@@ -38,34 +43,45 @@ public class HumanDeviceNames {
humanReadableNames.put("Galaxy_Nexus","Samsung Galaxy Nexus");
humanReadableNames.put("google_sdk","Android Emulator");
humanReadableNames.put("GT-I8160","Samsung Galaxy Ace 2");
humanReadableNames.put("GT-I8190N","Samsung Galaxy S III Mini");
humanReadableNames.put("GT-I8190","Samsung Galaxy S III Mini");
humanReadableNames.put("GT-I9000","Samsung Galaxy S");
humanReadableNames.put("GT-I9001","Samsung Galaxy S Plus");
humanReadableNames.put("GT-I9070","Samsung Galaxy S Advance");
humanReadableNames.put("GT-I9082","Samsung Galaxy Grand");
humanReadableNames.put("GT-I9100M","Samsung Galaxy S II");
humanReadableNames.put("GT-I9100P","Samsung Galaxy S II");
humanReadableNames.put("GT-I9100","Samsung Galaxy S II");
humanReadableNames.put("GT-I9100T","Samsung Galaxy S II");
humanReadableNames.put("GT-I9195","Samsung Galaxy S4 Mini");
humanReadableNames.put("GT-I9300","Samsung Galaxy S III");
humanReadableNames.put("GT-I9300T","Samsung Galaxy S III");
humanReadableNames.put("GT-I9305","Samsung Galaxy S III");
humanReadableNames.put("GT-I9505","Samsung Galaxy S 4");
humanReadableNames.put("GT-I9305T","Samsung Galaxy S III");
humanReadableNames.put("GT-I9500","Samsung Galaxy S4");
humanReadableNames.put("GT-I9505","Samsung Galaxy S4");
humanReadableNames.put("GT-N5110","Samsung Galaxy Note 8.0");
humanReadableNames.put("GT-N7000","Samsung Galaxy Note");
humanReadableNames.put("GT-N7100","Samsung Galaxy Note II");
humanReadableNames.put("GT-N7105","Samsung Galaxy Note II");
humanReadableNames.put("GT-N8013","Samsung Galaxy Note 10.1");
humanReadableNames.put("GT-P3113","Samsung Galaxy Tab 2 7.0");
humanReadableNames.put("GT-P5113","Samsnung Galaxy Tab 2 10.1");
humanReadableNames.put("GT-P5210","Samsung Galaxy Tab 3 10.1");
humanReadableNames.put("GT-P7510","Samsung Galaxy Tab 10.1");
humanReadableNames.put("GT-S5360","Samsung Galaxy Y");
humanReadableNames.put("GT-S5570","Samsung Galaxy Mini");
humanReadableNames.put("GT-S5830i","Samsung Galaxy Ace");
humanReadableNames.put("GT-S5830","Samsung Galaxy Ace");
humanReadableNames.put("GT-S7562","Samsung Galaxy S Duos");
humanReadableNames.put("HTC6435LVW","HTC Droid DNA");
humanReadableNames.put("HTC6500LVW","HTC One");
humanReadableNames.put("HTC_Desire_HD_A9191","HTC Desire HD");
humanReadableNames.put("HTCEVODesign4G","HTC Evo Design 4G");
humanReadableNames.put("HTCEVOV4G","HTC Evo V 4G");
humanReadableNames.put("HTCONE","HTC One");
humanReadableNames.put("HTC_PH39100","HTC Vivid 4G");
humanReadableNames.put("HTC_PN071","HTC One");
humanReadableNames.put("HTC_Sensation_Z710e","HTC Sensation");
humanReadableNames.put("HTC_VLE_U","HTC One S");
humanReadableNames.put("KFJWA","Kindle Fire HD 8.9");
@@ -73,9 +89,17 @@ public class HumanDeviceNames {
humanReadableNames.put("KFOT","Kindle Fire");
humanReadableNames.put("KFTT","Kindle Fire HD 7");
humanReadableNames.put("LG-C800","LG myTouch Q");
humanReadableNames.put("LG-D800","LG G2");
humanReadableNames.put("LG-D801","LG G2");
humanReadableNames.put("LG-D802","LG G2");
humanReadableNames.put("LG-E739","LG MyTouch e739");
humanReadableNames.put("LG-E970","LG Optimus G");
humanReadableNames.put("LG-E980","LG Optimus G Pro");
humanReadableNames.put("LGL55C","LG LGL55C");
humanReadableNames.put("LG-LS840","LG Viper");
humanReadableNames.put("LG-LS970","LG Optimus G");
humanReadableNames.put("LG-LS980","LG G2");
humanReadableNames.put("LGMS769","LG Optimus L9");
humanReadableNames.put("LG-MS770","LG Motion 4G");
humanReadableNames.put("LG-MS910","LG Esteem");
humanReadableNames.put("LG-P509","LG Optimus T");
@@ -91,18 +115,20 @@ public class HumanDeviceNames {
humanReadableNames.put("MOTWX435KT","Motorola Triumph");
humanReadableNames.put("myTouch_4G_Slide","HTC myTouch 4G Slide");
humanReadableNames.put("N860","ZTE Warp N860");
humanReadableNames.put("Nexus_10","Google Nexus 10");
humanReadableNames.put("Nexus_4","Google Nexus 4");
humanReadableNames.put("Nexus_7","Asus Nexus 7");
humanReadableNames.put("Nexus_S_4G","Samsung Nexus S 4G");
humanReadableNames.put("Nexus_S","Samsung Nexus S");
humanReadableNames.put("Nexus_10","Nexus 10");
humanReadableNames.put("Nexus_4","Nexus 4");
humanReadableNames.put("Nexus_5","Nexus 5");
humanReadableNames.put("Nexus_7","Nexus 7");
humanReadableNames.put("Nexus_S_4G","Nexus S 4G");
humanReadableNames.put("Nexus_S","Nexus S");
humanReadableNames.put("PantechP9070","Pantech Burst");
humanReadableNames.put("PC36100","HTC Evo 4G");
humanReadableNames.put("PG06100","HTC EVO Shift 4G");
humanReadableNames.put("PG86100","HTC Evo 3D");
humanReadableNames.put("PH44100","HTC Evo Design 4G");
humanReadableNames.put("SAMSUNG-SGH-I317","Samsung Galaxy Note II");
humanReadableNames.put("SAMSUNG-SGH-I337","Samsung Galaxy S 4");
humanReadableNames.put("SAMSUNG-SGH-I337","Samsung Galaxy S4");
humanReadableNames.put("SAMSUNG-SGH-I537","Samsung Galaxy S4 Active");
humanReadableNames.put("SAMSUNG-SGH-I717","Samsung Galaxy Note");
humanReadableNames.put("SAMSUNG-SGH-I727","Samsung Skyrocket");
humanReadableNames.put("SAMSUNG-SGH-I747","Samsung Galaxy S III");
@@ -110,22 +136,27 @@ public class HumanDeviceNames {
humanReadableNames.put("SAMSUNG-SGH-I897","Samsung Captivate");
humanReadableNames.put("SAMSUNG-SGH-I927","Samsung Captivate Glide");
humanReadableNames.put("SAMSUNG-SGH-I997","Samsung Infuse 4G");
humanReadableNames.put("SAMSUNG-SM-N900A","Samsung Galaxy Note 3");
humanReadableNames.put("SCH-I200","Samsung Galaxy Stellar");
humanReadableNames.put("SCH-I405","Samsung Stratosphere");
humanReadableNames.put("SCH-I415","Samsung Galaxy Stratosphere II");
humanReadableNames.put("SCH-I500","Samsung Fascinate");
humanReadableNames.put("SCH-I510","Samsung Droid Charge");
humanReadableNames.put("SCH-I535","Samsung Galaxy S III");
humanReadableNames.put("SCH-I545","Samsung Galaxy S 4");
humanReadableNames.put("SCH-I545","Samsung Galaxy S4");
humanReadableNames.put("SCH-I605","Samsung Galaxy Note II");
humanReadableNames.put("SCH-I800","Samsung Galaxy Tab 7.0");
humanReadableNames.put("SCH-R530M","Samsung Galaxy S III");
humanReadableNames.put("SCH-R530U","Samsung Galaxy S III");
humanReadableNames.put("SCH-R720","Samsung Admire");
humanReadableNames.put("SCH-S720C","Samsung Proclaim");
humanReadableNames.put("SCH-S738C","Samsung Galaxy Centura");
humanReadableNames.put("SGH-I317M","Samsung Galaxy Note II");
humanReadableNames.put("SGH-I337M","Samsung Galaxy S4");
humanReadableNames.put("SGH-I727R","Samsung Galaxy S II");
humanReadableNames.put("SGH-I747M","Samsung Galaxy S III");
humanReadableNames.put("SGH-M919","Samsung Galaxy S 4");
humanReadableNames.put("SGH-M919","Samsung Galaxy S4");
humanReadableNames.put("SGH-T599N","Samsung Galaxy Exhibit");
humanReadableNames.put("SGH-T679","Samsung Exhibit II");
humanReadableNames.put("SGH-T769","Samsung Galaxy S Blaze");
humanReadableNames.put("SGH-T889","Samsung Galaxy Note II");
@@ -133,28 +164,48 @@ public class HumanDeviceNames {
humanReadableNames.put("SGH-T959V","Samsung Galaxy S 4G");
humanReadableNames.put("SGH-T989D","Samsung Galaxy S II");
humanReadableNames.put("SGH-T989","Samsung Galaxy S II");
humanReadableNames.put("SGH-T999L","Samsung Galaxy S III");
humanReadableNames.put("SGH-T999","Samsung Galaxy S III");
humanReadableNames.put("SGH-T999V","Samsung Galaxy S III");
humanReadableNames.put("SHV-E210S","Samsung Galaxy S III");
humanReadableNames.put("SM-N9005","Samsung Galaxy Note 3");
humanReadableNames.put("SM-N900P","Samsung Galaxy Note 3");
humanReadableNames.put("SM-N900T","Samsung Galaxy Note 3");
humanReadableNames.put("SM-N900V","Samsung Galaxy Note 3");
humanReadableNames.put("SM-N900W8","Samsung Galaxy Note 3");
humanReadableNames.put("SM-T210R","Samsung Galaxy Tab 3 7.0");
humanReadableNames.put("SM-T310","Samsung Galaxy Tab 3 8.0");
humanReadableNames.put("SPH-D600","Samsung Conquer 4G");
humanReadableNames.put("SPH-D700","Samsung Epic 4G");
humanReadableNames.put("SPH-D710BST","Samsung Galaxy S II");
humanReadableNames.put("SPH-D710","Samsung Epic");
humanReadableNames.put("SPH-D710VMUB","Samsung Galaxy S II");
humanReadableNames.put("SPH-L300","Samsung Galaxy Victory");
humanReadableNames.put("SPH-L710","Samsung Galaxy S III");
humanReadableNames.put("SPH-L720","Samsung Galaxy S 4");
humanReadableNames.put("SPH-L720","Samsung Galaxy S4");
humanReadableNames.put("SPH-L900","Samsung Galaxy Note II");
humanReadableNames.put("SPH-M820-BST","Samsung Galaxy Prevail");
humanReadableNames.put("SPH-M830","Samsung Galaxy Rush");
humanReadableNames.put("SPH-M930BST","Samsung Transform Ultra");
humanReadableNames.put("ST25i","Sony Xperia U");
humanReadableNames.put("Transformer_Prime_TF201","Asus Eee Pad Transformer Prime");
humanReadableNames.put("Transformer_TF101","Asus Eee Pad Transformer");
humanReadableNames.put("VM670","LG Optimus V");
humanReadableNames.put("VS840_4G","LG Lucid 4G");
humanReadableNames.put("VS910_4G","LG Revolution 4G");
humanReadableNames.put("VS920_4G","LG Spectrum 4G");
humanReadableNames.put("VS980_4G","LG G2");
humanReadableNames.put("Xoom","Motorola Xoom");
humanReadableNames.put("XT1030","Motorola Droid Mini");
humanReadableNames.put("XT1032","Motorola Moto G");
humanReadableNames.put("XT1058","Motorola Moto X");
humanReadableNames.put("XT1060","Motorola Moto X");
humanReadableNames.put("XT1080","Motorola Droid Ultra");
humanReadableNames.put("XT897","Motorola Photo Q");
humanReadableNames.put("XT907","Motorola Droid Razr M");
}
static String getDeviceName() {
public static String getDeviceName() {
String dictName = humanReadableNames.get(Build.MODEL.replace(' ','_'));
if (dictName != null) return dictName;
@@ -167,4 +218,11 @@ public class HumanDeviceNames {
}
public static boolean isTablet() {
Configuration config = Resources.getSystem().getConfiguration();
//This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE
boolean isLarge = ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE);
return isLarge;
}
}

View File

@@ -0,0 +1,56 @@
package org.kde.kdeconnect.Helpers;
import android.webkit.MimeTypeMap;
public class FilesHelper {
public static String getFileExt(String fileName) {
//return MimeTypeMap.getFileExtensionFromUrl(fileName);
return fileName.substring((fileName.lastIndexOf(".") + 1), fileName.length());
}
public static String getMimeTypeFromFile(String file) {
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExt(file));
if (mime == null) mime = "*/*";
return mime;
}
//Following code from http://activemq.apache.org/maven/5.7.0/kahadb/apidocs/src-html/org/apache/kahadb/util/IOHelper.html
/**
* Converts any string into a string that is safe to use as a file name.
* The result will only include ascii characters and numbers, and the "-","_", and "." characters.
*
* @param name
* @param dirSeparators
* @param maxFileLength
* @return
*/
public static String toFileSystemSafeName(String name, boolean dirSeparators, int maxFileLength) {
int size = name.length();
StringBuilder rc = new StringBuilder(size * 2);
for (int i = 0; i < size; i++) {
char c = name.charAt(i);
boolean valid = c >= 'a' && c <= 'z';
valid = valid || (c >= 'A' && c <= 'Z');
valid = valid || (c >= '0' && c <= '9');
valid = valid || (c == '_') || (c == '-') || (c == '.');
valid = valid || (dirSeparators && ( (c == '/') || (c == '\\')));
if (valid) {
rc.append(c);
}
}
String result = rc.toString();
if (result.length() > maxFileLength) {
result = result.substring(result.length()-maxFileLength,result.length());
}
return result;
}
public static String toFileSystemSafeName(String name, boolean dirSeparators) {
return toFileSystemSafeName(name, dirSeparators, 255);
}
public static String toFileSystemSafeName(String name) {
return toFileSystemSafeName(name, true, 255);
}
}

View File

@@ -8,13 +8,12 @@ import android.util.Base64;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -26,15 +25,18 @@ public class NetworkPackage {
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_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;
@@ -74,6 +76,9 @@ public class NetworkPackage {
public double getDouble(String key) { return mBody.optDouble(key,Double.NaN); }
public double getDouble(String key, double defaultValue) { return mBody.optDouble(key,defaultValue); }
public void set(String key, double value) { try { mBody.put(key,value); } catch(Exception e) { } }
public JSONArray getJSONArray(String key) { return mBody.optJSONArray(key); }
public void set(String key, JSONArray value) { try { mBody.put(key,value); } catch(Exception e) { } }
public ArrayList<String> getStringList(String key) {
JSONArray jsonArray = mBody.optJSONArray(key);
ArrayList<String> list = new ArrayList<String>();
@@ -103,7 +108,6 @@ public class NetworkPackage {
}
}
public boolean has(String key) { return mBody.has(key); }
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); }
@@ -161,7 +165,7 @@ public class NetworkPackage {
return np;
}
public void encrypt(PublicKey publicKey) throws Exception {
public NetworkPackage encrypt(PublicKey publicKey) throws Exception {
String serialized = serialize();
@@ -183,17 +187,11 @@ public class NetworkPackage {
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
}
mId = System.currentTimeMillis();
mType = NetworkPackage.PACKAGE_TYPE_ENCRYPTED;
mBody = new JSONObject();
try {
mBody.put("data", chunks);
}catch(Exception e){
e.printStackTrace();
Log.e("NetworkPackage","Exception");
}
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED);
encrypted.set("data", chunks);
return encrypted;
}
@@ -221,15 +219,22 @@ public class NetworkPackage {
String deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
try {
np.mBody.put("deviceId", deviceId);
np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
np.mBody.put("deviceName",
PreferenceManager.getDefaultSharedPreferences(context).getString(
MainSettingsActivity.KEY_DEVICE_NAME_PREFERENCE,
DeviceHelper.getDeviceName()));
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
} catch (JSONException e) {
np.mBody.put("deviceType", DeviceHelper.isTablet()? "tablet" : "phone");
} catch (Exception e) {
e.printStackTrace();
Log.e("NetworkPacakge","Exception on createIdentityPackage");
}
return np;
}
static public NetworkPackage createPublicKeyPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.BatteryManager;
import android.util.Log;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
@@ -17,9 +16,11 @@ import org.kde.kdeconnect_tp.R;
public class BatteryPlugin extends Plugin {
private NetworkPackage lastPackage = null;
// 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;
private IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
private NetworkPackage lastInfo = null;
/*static {
PluginFactory.registerPlugin(BatteryPlugin.class);
@@ -50,36 +51,36 @@ public class BatteryPlugin extends Plugin {
return true;
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
public void onReceive(Context context, Intent batteryIntent) {
Log.i("BatteryPlugin", "Battery event");
Intent batteryChargeIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int level = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int currentCharge = level*100 / scale;
boolean isCharging = (0 != batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
boolean lowBattery = Intent.ACTION_BATTERY_LOW.equals(batteryIntent.getAction());
int thresholdEvent = lowBattery? THRESHOLD_EVENT_BATTERY_LOW : THRESHOLD_EVENT_NONE;
boolean isCharging = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
int currentCharge = 100;
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status != BatteryManager.BATTERY_STATUS_FULL) {
Intent batteryStatus = context.registerReceiver(null, filter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
currentCharge = level*100 / scale;
}
//Only notify if change is meaningful enough
if (lastPackage == null
|| (
isCharging != lastPackage.getBoolean("isCharging")
|| currentCharge != lastPackage.getInt("currentCharge")
)
if (lastInfo != null
&& isCharging != lastInfo.getBoolean("isCharging")
&& currentCharge != lastInfo.getInt("currentCharge")
&& thresholdEvent != lastInfo.getInt("thresholdEvent")
) {
//Do not send again if nothing has changed
return;
} else {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
np.set("isCharging", isCharging);
np.set("currentCharge", currentCharge);
np.set("isCharging", isCharging);
np.set("thresholdEvent", thresholdEvent);
device.sendPackage(np);
lastPackage = np;
lastInfo = np;
}
}
@@ -87,12 +88,14 @@ public class BatteryPlugin extends Plugin {
@Override
public boolean onCreate() {
context.registerReceiver(receiver, filter);
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_LOW));
return true;
}
@Override
public void onDestroy() {
//It's okay to call this only once, even though we registered it for two filters
context.unregisterReceiver(receiver);
}
@@ -101,8 +104,8 @@ public class BatteryPlugin extends Plugin {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false;
if (np.getBoolean("request")) {
if (lastPackage != null) {
device.sendPackage(lastPackage);
if (lastInfo != null) {
device.sendPackage(lastInfo);
}
}

View File

@@ -10,12 +10,18 @@ import org.kde.kdeconnect.NetworkPackage;
public class ClipboardListener {
private final Context context;
private String currentContent;
private ClipboardManager cm = null;
ClipboardManager.OnPrimaryClipChangedListener listener;
private ClipboardManager.OnPrimaryClipChangedListener listener;
ClipboardListener(final Context ctx, final Device device) {
context = ctx;
if(android.os.Build.VERSION.SDK_INT < 11) {
return;
}
ClipboardListener(final Context context, final Device device) {
cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
@@ -41,12 +47,24 @@ public class ClipboardListener {
}
public void stop() {
if(android.os.Build.VERSION.SDK_INT < 11) {
return;
}
cm.removePrimaryClipChangedListener(listener);
}
@SuppressWarnings("deprecation")
public void setText(String text) {
currentContent = text;
cm.setText(text);
if(android.os.Build.VERSION.SDK_INT < 11) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
else
{
cm.setText(text);
}
}
}

View File

@@ -3,9 +3,7 @@ package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
@@ -39,31 +37,21 @@ public class ClipboardPlugin extends Plugin {
@Override
public boolean isEnabledByDefault() {
return (Build.VERSION.SDK_INT >= 11);
//Disabled by default due to just one direction sync(incoming clipboard change) in early version of android.
return (android.os.Build.VERSION.SDK_INT >= 11);
}
private ClipboardListener listener;
@Override
public boolean onCreate() {
if (Build.VERSION.SDK_INT < 11) {
return false;
}
listener = new ClipboardListener(context, device);
return true;
}
@Override
public void onDestroy() {
if (Build.VERSION.SDK_INT < 11) return;
listener.stop();
}
@Override
@@ -76,22 +64,10 @@ public class ClipboardPlugin extends Plugin {
String content = np.getString("content");
listener.setText(content);
return true;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return new AlertDialog.Builder(baseContext)
.setTitle(R.string.pref_plugin_clipboard)
.setMessage(R.string.plugin_not_available)
.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.create();
}
public AlertDialog getErrorDialog(Context baseContext) { return null; }
@Override
public Button getInterfaceButton(Activity activity) {

View File

@@ -0,0 +1,231 @@
package org.kde.kdeconnect.Plugins.MousePadPlugin;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect_tp.R;
public class MousePadActivity extends Activity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener {
private final static float MinDistanceToSendScroll = 2.5f;
private float mPrevX;
private float mPrevY;
private float mCurrentX;
private float mCurrentY;
boolean isScrolling = false;
float accumulatedDistanceY = 0;
private String deviceId;
private GestureDetector mDetector;
private MousePadGestureDetector mMousePadGestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mousepad);
deviceId = getIntent().getStringExtra("deviceId");
mDetector = new GestureDetector(this, this);
mMousePadGestureDetector = new MousePadGestureDetector(this, this);
mDetector.setOnDoubleTapListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_mousepad, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_right_click:
sendRightClick();
return true;
case R.id.menu_middle_click:
sendMiddleClick();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mMousePadGestureDetector.onTouchEvent(event)) {
return true;
}
if ( mDetector.onTouchEvent(event) ) {
return true;
}
int actionType = event.getAction();
final float x = event.getX();
final float y = event.getY();
if (isScrolling) {
if (actionType == MotionEvent.ACTION_UP) {
isScrolling = false;
} else {
return false;
}
}
switch (actionType) {
case MotionEvent.ACTION_DOWN:
mPrevX = x;
mPrevY = y;
break;
case MotionEvent.ACTION_MOVE:
mCurrentX = x;
mCurrentY = y;
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendPoints(mCurrentX - mPrevX, mCurrentY - mPrevY);
mPrevX = mCurrentX;
mPrevY = mCurrentY;
}
});
break;
}
return true;
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) {
// If only one thumb is used then cancel the scroll gesture
if (e2.getPointerCount() <= 1) {
return false;
}
isScrolling = true;
accumulatedDistanceY += distanceY;
if (accumulatedDistanceY > MinDistanceToSendScroll || accumulatedDistanceY < -MinDistanceToSendScroll)
{
final float scrollToSendY = accumulatedDistanceY;
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendScroll(0, scrollToSendY);
}
});
accumulatedDistanceY = 0;
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendSingleClick();
}
});
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendDoubleClick();
}
});
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
@Override
public boolean onTripleFingerTap(MotionEvent ev) {
sendMiddleClick();
return true;
}
@Override
public boolean onDoubleFingerTap(MotionEvent ev) {
sendRightClick();
return true;
}
private void sendMiddleClick() {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendMiddleClick();
}
});
}
private void sendRightClick() {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendRightClick();
}
});
}
}

View File

@@ -0,0 +1,59 @@
package org.kde.kdeconnect.Plugins.MousePadPlugin;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public class MousePadGestureDetector {
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout() + 100;
private OnGestureListener mGestureListener;
private Context mCtx;
private long mFirstDownTime = 0;
private boolean mIsGestureHandled;
public interface OnGestureListener {
boolean onTripleFingerTap(MotionEvent ev);
boolean onDoubleFingerTap(MotionEvent ev);
}
public MousePadGestureDetector(Context ctx, OnGestureListener gestureListener) {
if (gestureListener == null) {
throw new IllegalArgumentException("gestureListener cannot be null");
}
mGestureListener = gestureListener;
mCtx = ctx;
}
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mIsGestureHandled = false;
mFirstDownTime = event.getEventTime();
break;
case MotionEvent.ACTION_POINTER_UP:
int count = event.getPointerCount();
if (event.getEventTime() - mFirstDownTime <= TAP_TIMEOUT) {
if (count == 3) {
if (!mIsGestureHandled) {
mIsGestureHandled = mGestureListener.onTripleFingerTap(event);
}
} else if (count == 2) {
if (!mIsGestureHandled) {
mIsGestureHandled = mGestureListener.onDoubleFingerTap(event);
}
}
}
mFirstDownTime = 0;
break;
}
return mIsGestureHandled;
}
}

View File

@@ -0,0 +1,112 @@
package org.kde.kdeconnect.Plugins.MousePadPlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
public class MousePadPlugin extends Plugin {
@Override
public String getPluginName() {
return "plugin_mousepad";
}
@Override
public String getDisplayName() {
return context.getString(R.string.pref_plugin_mousepad);
}
@Override
public String getDescription() {
return context.getString(R.string.pref_plugin_mousepad_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public void onDestroy() {
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
return false;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) { return null; }
public void sendPoints(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(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("singleclick", true);
device.sendPackage(np);
}
public void sendDoubleClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("doubleclick", true);
device.sendPackage(np);
}
public void sendMiddleClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("middleclick", true);
device.sendPackage(np);
}
public void sendRightClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("rightclick", true);
device.sendPackage(np);
}
public void sendScroll(float dx, float dy) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("scroll", true);
np.set("dx", dx);
np.set("dy", dy);
device.sendPackage(np);
}
@Override
public Button getInterfaceButton(final Activity activity) {
Button button = new Button(activity);
button.setText(R.string.open_mousepad);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(activity, MousePadActivity.class);
intent.putExtra("deviceId", device.getDeviceId());
activity.startActivity(intent);
}
});
return button;
}
}

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -24,9 +25,11 @@ import java.util.ArrayList;
public class MprisActivity extends Activity {
//TODO: Add a loading spinner at the begginning (to distinguish the loading state from a no-players state).
//TODO: Add a loading spinner at the beginning (to distinguish the loading state from a no-players state).
//TODO 2: Add a message when no players are detected after loading completes
private String deviceId;
protected void connectToPlugin() {
final String deviceId = getIntent().getStringExtra("deviceId");
@@ -93,8 +96,12 @@ public class MprisActivity extends Activity {
//Spotify doesn't support changing the volume yet...
if (player.equals("Spotify")) {
findViewById(R.id.volume_layout).setVisibility(View.INVISIBLE);
findViewById(R.id.rew_button).setVisibility(View.GONE);
findViewById(R.id.ff_button).setVisibility(View.GONE);
} else {
findViewById(R.id.volume_layout).setVisibility(View.VISIBLE);
findViewById(R.id.rew_button).setVisibility(View.VISIBLE);
findViewById(R.id.ff_button).setVisibility(View.VISIBLE);
}
}
@@ -119,7 +126,7 @@ public class MprisActivity extends Activity {
}
BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) {
connectToPlugin();
@@ -142,12 +149,73 @@ public class MprisActivity extends Activity {
});
}
/**
* Change current volume with provided step.
*
* @param mpris multimedia controller
* @param step step size volume change
*/
private void updateVolume(MprisPlugin mpris, int step) {
final int currentVolume = mpris.getVolume();
if(currentVolume < 100 || currentVolume > 0) {
int newVolume = currentVolume + step;
if(newVolume > 100) {
newVolume = 100;
} else if (newVolume <0 ) {
newVolume = 0;
}
mpris.setVolume(newVolume);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin) device.getPlugin("plugin_mpris");
if (mpris == null) return;
updateVolume(mpris, 5);
}
});
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin) device.getPlugin("plugin_mpris");
if (mpris == null) return;
updateVolume(mpris, -5);
}
});
return true;
default:
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
return true;
default:
return super.onKeyUp(keyCode, event);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mpris_control);
final String deviceId = getIntent().getStringExtra("deviceId");
deviceId = getIntent().getStringExtra("deviceId");
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
@@ -187,6 +255,36 @@ public class MprisActivity extends Activity {
}
});
findViewById(R.id.rew_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(-10000000); // -10 seconds. TODO: plugin settings UI?
}
});
}
});
findViewById(R.id.ff_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(10000000); // 10 seconds. TODO: plugin settings UI?
}
});
}
});
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -204,10 +302,12 @@ public class MprisActivity extends Activity {
((SeekBar)findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) { }
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { }
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
@@ -219,12 +319,14 @@ public class MprisActivity extends Activity {
if (mpris == null) return;
mpris.setVolume(seekBar.getProgress());
}
});
});
}
});
}
}

View File

@@ -84,6 +84,13 @@ public class MprisPlugin extends Plugin {
device.sendPackage(np);
}
public void Seek(int offset) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
np.set("player",player);
np.set("Seek",offset);
device.sendPackage(np);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false;

View File

@@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import java.util.ArrayList;
@@ -16,7 +15,7 @@ public class NotificationReceiver extends NotificationListenerService {
void onNotificationRemoved(StatusBarNotification statusBarNotification);
}
private ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
private final ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
public void addListener(NotificationListener listener) {
listeners.add(listener);
@@ -27,7 +26,7 @@ public class NotificationReceiver extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
Log.i("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
//Log.e("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
for(NotificationListener listener : listeners) {
listener.onNotificationPosted(statusBarNotification);
}
@@ -49,7 +48,7 @@ public class NotificationReceiver extends NotificationListenerService {
//This will be called for each intent launch, even if the service is already started and is reused
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("NotificationReceiver", "onStartCommand");
//Log.e("NotificationReceiver", "onStartCommand");
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
}
@@ -61,7 +60,7 @@ public class NotificationReceiver extends NotificationListenerService {
void onServiceStart(NotificationReceiver service);
}
private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
private final static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
public static void Start(Context c) {
RunCommand(c, null);

View File

@@ -6,25 +6,19 @@ import android.app.Notification;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Base64;
import android.util.Log;
import android.widget.Button;
import org.kde.kdeconnect.Helpers.AppsHelper;
import org.kde.kdeconnect.Helpers.ImagesHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
/*static {
@@ -116,7 +110,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public boolean onCreate() {
if (Build.VERSION.SDK_INT < 18) return false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
//Check for permissions
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
@@ -126,12 +122,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
public void onServiceStart(NotificationReceiver service) {
try {
service.addListener(NotificationsPlugin.this);
/*
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
onNotificationPosted(notification);
sendNotification(notification, true);
}
*/
} catch(Exception e) {
e.printStackTrace();
Log.e("NotificationsPlugin","Exception");
@@ -148,6 +142,11 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return;
}
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
@@ -178,6 +177,13 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) {
Notification notification = statusBarNotification.getNotification();
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
|| (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0 ) {
//This is not a notification!
return;
}
NotificationId id = NotificationId.fromNotification(statusBarNotification);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
@@ -204,7 +210,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
np.set("id", id.serialize());
np.set("appName", appName == null? packageName : appName);
np.set("isClearable", statusBarNotification.isClearable());
np.set("ticker", notification.tickerText.toString());
np.set("ticker", getTickerText(notification));
np.set("time", Long.toString(statusBarNotification.getPostTime()));
if (requestAnswer) np.set("requestAnswer", true);
@@ -212,7 +218,43 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
/**
* Returns the ticker text of the notification.
* If device android version is KitKat or newer, the title and text of the notification is used
* instead the ticker text.
*/
private String getTickerText(Notification notification) {
final String TITLE_KEY = "android.title";
final String TEXT_KEY = "android.text";
String ticker = "";
if(notification != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
Bundle extras = notification.extras;
String extraTitle = extras.getString(TITLE_KEY);
String extraText = extras.getString(TEXT_KEY);
if (extraTitle != null && extraText != null) {
ticker = extraTitle + " " + extraText;
} else if (extraTitle != null) {
ticker = extraTitle;
} else if (extraText != null) {
ticker = extraText;
}
} catch(Exception e) {
Log.w("NotificationPlugin","problem parsing notification extras");
e.printStackTrace();
}
}
if (ticker.isEmpty()) {
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
}
}
return ticker;
}
@Override

View File

@@ -4,14 +4,18 @@ 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.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.view.View;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect_tp.R;
@@ -63,9 +67,18 @@ public class PingPlugin extends Plugin {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
//Log.e("PingPackageReceiver", "was a ping!");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(new Intent(context, MainActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName())
.setContentText("Ping!")
.setContentIntent(resultPendingIntent)
.setTicker("Ping!")
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true)

View File

@@ -7,10 +7,13 @@ import android.util.Log;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
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.SharePlugin.SharePlugin;
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
import java.util.Map;
@@ -49,11 +52,11 @@ public class PluginFactory {
return enabledByDefault;
}
private String pluginName;
private String displayName;
private String description;
private final String pluginName;
private final String displayName;
private final String description;
private final Drawable icon;
private boolean enabledByDefault;
private final boolean enabledByDefault;
}
@@ -67,7 +70,10 @@ public class PluginFactory {
PluginFactory.registerPlugin(MprisPlugin.class);
PluginFactory.registerPlugin(ClipboardPlugin.class);
PluginFactory.registerPlugin(BatteryPlugin.class);
PluginFactory.registerPlugin(SftpPlugin.class);
PluginFactory.registerPlugin(NotificationsPlugin.class);
PluginFactory.registerPlugin(MousePadPlugin.class);
PluginFactory.registerPlugin(SharePlugin.class);
}
public static PluginInfo getPluginInfo(Context context, String pluginName) {
@@ -109,10 +115,10 @@ public class PluginFactory {
}
public static void registerPlugin(Class pluginClass) {
public static void registerPlugin(Class<? extends Plugin> pluginClass) {
try {
//I hate this but I need to create an instance because abstract static functions can't be declared
String pluginName = ((Plugin)pluginClass.newInstance()).getPluginName();
String pluginName = (pluginClass.newInstance()).getPluginName();
availablePlugins.put(pluginName, pluginClass);
} catch(Exception e) {
Log.e("PluginFactory","addPlugin exception");

View File

@@ -0,0 +1,210 @@
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.server.Command;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.filesystem.NativeFileSystemView;
import org.apache.sshd.server.filesystem.NativeSshFile;
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 java.io.File;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
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 List<PublicKey> keys = new ArrayList<PublicKey>();
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;
private static final String USER = "kdeconnect";
public static int port = -1;
private static boolean started = false;
public final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
public final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
private final SshServer sshd = SshServer.setUpDefaultServer();
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(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
sshd.setPasswordAuthenticator(passwordAuth);
sshd.setPublickeyAuthenticator(keyAuth);
}
public boolean start() {
if (!started) {
String password = Long.toHexString(Double.doubleToLongBits(Math.random()));
passwordAuth.setPassword(password);
port = STARTPORT;
while(!started) {
try {
sshd.setPort(port);
sshd.start();
started = true;
} catch(Exception e) {
port++;
if (port >= ENDPORT) {
port = -1;
Log.e("SftpServer", "No more ports available");
return false;
}
}
}
}
return true;
}
public void stop() {
try {
started = false;
sshd.stop();
} catch (InterruptedException e) {
}
}
public String getLocalIpAddress() {
String ip6 = null;
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
String address = inetAddress.getHostAddress();
if (InetAddressUtils.isIPv4Address(address)) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
return address;
} else {
ip6 = address;
}
}
}
}
} catch (SocketException ex) {
}
return ip6;
}
}
class SecureFileSystemFactory implements FileSystemFactory {
public SecureFileSystemFactory() {}
@Override
public FileSystemView createFileSystemView(final Session username) {
final String base = "/";
return new SecureFileSystemView(base, username.getUsername());
}
}
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 = "/";
private String rootDir = "/";
private String userName;
private boolean caseInsensitive = false;
//
public SecureFileSystemView(final String rootDir, final String userName) {
super(userName);
this.rootDir = NativeSshFile.normalizeSeparateChar(rootDir);
this.userName = userName;
}
//
@Override
public SshFile getFile(final String file) {
return getFile(currDir, file);
}
@Override
public SshFile getFile(final SshFile baseDir, final String file) {
return getFile(baseDir.getAbsolutePath(), file);
}
//
protected SshFile getFile(final String dir, final String file) {
// get actual file object
String physicalName = NativeSshFile.getPhysicalName("/", dir, file, caseInsensitive);
File fileObj = new File(rootDir, physicalName); // chroot
// strip the root directory and return
String userFileName = physicalName.substring("/".length() - 1);
return new SecureSshFile(this, userFileName, fileObj, userName);
}
}
class SecureSshFile extends NativeSshFile {
//
public SecureSshFile(final SecureFileSystemView view, final String fileName, final File file, final String userName) {
super(fileName, file, userName);
}
}

View File

@@ -0,0 +1,79 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
public class SftpPlugin extends Plugin {
private static final SimpleSftpServer server = new SimpleSftpServer();
/*static {
PluginFactory.registerPlugin(SftpPlugin.class);
}*/
@Override
public String getPluginName() {return "plugin_sftp";}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sftp);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_sftp_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {return true;}
@Override
public boolean onCreate() {
server.init(context, device);
return true;
}
@Override
public void onDestroy() {
server.stop();
}
@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(NetworkPackage.PACKAGE_TYPE_SFTP);
np2.set("ip", server.getLocalIpAddress());
np2.set("port", server.port);
np2.set("user", server.passwordAuth.getUser());
np2.set("password", server.passwordAuth.getPassword());
np2.set("path", Environment.getExternalStorageDirectory().getAbsolutePath());
device.sendPackage(np2);
return true;
}
}
return false;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {return null;}
@Override
public Button getInterfaceButton(Activity activity) {return null;}
}

View File

@@ -0,0 +1,243 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import org.kde.kdeconnect.Helpers.FilesHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class SharePlugin extends Plugin {
/*static {
PluginFactory.registerPlugin(PingPlugin.class);
}*/
@Override
public String getPluginName() {
return "share_ping";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sharereceiver);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_sharereceiver_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public void onDestroy() {
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SHARE)) {
return false;
}
try {
if (np.hasPayload()) {
Log.e("SharePlugin", "hasPayload");
final InputStream input = np.getPayload();
final int fileLength = np.getPayloadSize();
final String filename = np.getString("filename", new Long(System.currentTimeMillis()).toString());
String deviceDir = FilesHelper.toFileSystemSafeName(device.getName());
//Get the external storage and append "/kdeconnect/DEVICE_NAME/"
String destinationDir = Environment.getExternalStorageDirectory().getPath();
destinationDir = new File(destinationDir, "kdeconnect").getPath();
destinationDir = new File(destinationDir, deviceDir).getPath();
//Create directories if needed
new File(destinationDir).mkdirs();
//Append filename to the destination path
final File destinationFullPath = new File(destinationDir, filename);
Log.e("SharePlugin", "destinationFullPath:" + destinationFullPath);
final int notificationId = (int)System.currentTimeMillis();
Resources res = context.getResources();
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.incoming_file_title, device.getName()))
.setContentText(res.getString(R.string.incoming_file_text, filename))
.setTicker(res.getString(R.string.incoming_file_title, device.getName()))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, noti);
new Thread(new Runnable() {
@Override
public void run() {
try {
OutputStream output = new FileOutputStream(destinationFullPath.getPath());
byte data[] = new byte[1024];
long total = 0;
int count;
while ((count = input.read(data)) >= 0) {
total += count;
output.write(data, 0, count);
if (fileLength > 0) {
if (total >= fileLength) break;
float progress = (total * 100 / fileLength);
}
//else Log.e("SharePlugin", "Infinite loop? :D");
}
output.flush();
output.close();
input.close();
Log.e("SharePlugin", "Transfer finished");
//Make sure it is added to the Android Gallery
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(destinationFullPath));
context.sendBroadcast(mediaScanIntent);
//Update the notification and allow to open the file from it
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(destinationFullPath), FilesHelper.getMimeTypeFromFile(destinationFullPath.getPath()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(intent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Resources res = context.getResources();
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.received_file_title, device.getName()))
.setContentText(res.getString(R.string.received_file_text, filename))
.setContentIntent(resultPendingIntent)
.setTicker(res.getString(R.string.received_file_title, device.getName()))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, noti);
} catch (Exception e) {
Log.e("SharePlugin", "Receiver thread exception");
e.printStackTrace();
}
}
}).start();
} else if (np.has("text")) {
Log.e("SharePlugin", "hasText");
String text = np.getString("text");
if(android.os.Build.VERSION.SDK_INT >= 11) {
ClipboardManager cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
} else {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
} else if (np.has("url")) {
String url = np.getString("url");
Log.e("SharePlugin", "hasUrl: "+url);
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//Do not launch it directly, show a notification instead
//context.startActivity(browserIntent);
Resources res = context.getResources();
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(browserIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.received_url_title, device.getName()))
.setContentText(res.getString(R.string.received_url_text, url))
.setContentIntent(resultPendingIntent)
.setTicker(res.getString(R.string.received_url_title, device.getName()))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify((int)System.currentTimeMillis(), noti);
} else {
Log.e("SharePlugin", "Error: Nothing attached!");
}
} catch(Exception e) {
Log.e("SharePlugin","Exception");
e.printStackTrace();
}
return true;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return null;
}
@Override
public Button getInterfaceButton(Activity activity) { return null; }
}

View File

@@ -10,7 +10,6 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Button;
import org.kde.kdeconnect.Helpers.ContactsHelper;
@@ -49,7 +48,7 @@ public class TelephonyPlugin extends Plugin {
return true;
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -96,6 +95,8 @@ public class TelephonyPlugin extends Plugin {
public void callBroadcastReceived(int state, String phoneNumber) {
//Log.e("TelephonyPlugin", "callBroadcastReceived");
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY);
if (phoneNumber != null) {
phoneNumber = ContactsHelper.phoneNumberLookup(context,phoneNumber);
@@ -162,6 +163,7 @@ public class TelephonyPlugin extends Plugin {
@Override
public boolean onCreate() {
//Log.e("TelephonyPlugin", "onCreate");
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
context.registerReceiver(receiver, filter);

View File

@@ -0,0 +1,177 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.List.ButtonItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect.UserInterface.List.TextItem;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
public class DeviceActivity extends ActionBarActivity {
static private String deviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
private Device device;
private final Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
@Override
public void onPluginsChanged(final Device device) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
//Errors list
final HashMap<String, Plugin> failedPlugins = device.getFailedPlugins();
final String[] ids = failedPlugins.keySet().toArray(new String[failedPlugins.size()]);
String[] names = new String[failedPlugins.size()];
for(int i = 0; i < ids.length; i++) {
Plugin p = failedPlugins.get(ids[i]);
names[i] = p.getDisplayName();
}
ListView errorList = (ListView)findViewById(R.id.errors_list);
if (!failedPlugins.isEmpty() && errorList.getHeaderViewsCount() == 0) {
TextView header = new TextView(DeviceActivity.this);
header.setPadding(0,24,0,0);
header.setText(getResources().getString(R.string.plugins_failed_to_load));
errorList.addHeaderView(header);
}
errorList.setAdapter(new ArrayAdapter<String>(DeviceActivity.this, android.R.layout.simple_list_item_1, names));
errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (position == 0) return;
Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to subtract one
p.getErrorDialog(DeviceActivity.this).show();
}
});
//Buttons list
ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
if (device.isReachable()) {
final Collection<Plugin> plugins = device.getLoadedPlugins().values();
for (Plugin p : plugins) {
Button b = p.getInterfaceButton(DeviceActivity.this);
if (b != null) {
items.add(new SectionItem(p.getDisplayName()));
items.add(new ButtonItem(b));
}
}
} else {
Button b = new Button(DeviceActivity.this);
b.setText(R.string.device_menu_unpair);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
device.unpair();
finish();
}
});
items.add(new TextItem(getString(R.string.device_not_reachable)));
items.add(new ButtonItem(b));
}
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
} catch(ConcurrentModificationException e) {
Log.e("DeviceActivity", "ConcurrentModificationException");
this.run(); //Try again
}
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setDisplayHomeAsUpEnabled(true);
if (getIntent().hasExtra("deviceId")) {
deviceId = getIntent().getStringExtra("deviceId");
}
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
if (device == null) return;
setTitle(device.getName());
device.addPluginsChangedListener(pluginsChangedListener);
pluginsChangedListener.onPluginsChanged(device);
}
});
}
@Override
protected void onDestroy() {
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
device.removePluginsChangedListener(pluginsChangedListener);
}
});
super.onDestroy();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
if (device.isPaired()) {
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
intent.putExtra("deviceId", deviceId);
startActivity(intent);
return true;
}
});
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
device.unpair();
finish();
return true;
}
});
return true;
} else {
return false;
}
}
}

View File

@@ -1,15 +1,9 @@
package org.kde.kdeconnect.UserInterface.List;
import android.app.Activity;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.UserInterface.DeviceActivity;
import org.kde.kdeconnect.UserInterface.PairActivity;
import org.kde.kdeconnect_tp.R;
public class EntryItem implements ListAdapter.Item {

View File

@@ -14,8 +14,8 @@ public class ListAdapter extends ArrayAdapter<ListAdapter.Item> {
public View inflateView(LayoutInflater layoutInflater);
}
private ArrayList<Item> items;
private LayoutInflater layoutInflater;
private final ArrayList<Item> items;
private final LayoutInflater layoutInflater;
public ListAdapter(Context context, ArrayList<Item> items) {
super(context, 0, items);

View File

@@ -10,7 +10,7 @@ public class SectionItem implements ListAdapter.Item {
private final String title;
public boolean isEmpty;
public SectionItem(String title) {
this.title = title;
this.isEmpty = false;

View File

@@ -0,0 +1,27 @@
package org.kde.kdeconnect.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.R;
public class TextItem implements ListAdapter.Item {
private final String title;
public TextItem(String title) {
this.title = title;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
TextView v = new TextView(layoutInflater.getContext());
v.setText(title);
v.setTextAppearance(layoutInflater.getContext(), R.style.TextAppearance_DeviceDefault_Medium);
return v;
}
}

View File

@@ -1,11 +1,11 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -66,6 +66,9 @@ public class MainActivity extends ActionBarActivity {
}
}).start();
break;
case R.id.menu_settings:
startActivity(new Intent(this,MainSettingsActivity.class));
break;
default:
break;
}
@@ -79,7 +82,6 @@ public class MainActivity extends ActionBarActivity {
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
}
@@ -142,12 +144,13 @@ public class MainActivity extends ActionBarActivity {
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
view.callOnClick();
view.performClick();
}
});
}
});
}
});
}

View File

@@ -0,0 +1,112 @@
package org.kde.kdeconnect.UserInterface;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect_tp.R;
public class MainSettingsActivity extends PreferenceActivity {
public static final String KEY_DEVICE_NAME_PREFERENCE = "device_name_preference";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeDeviceName(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
addPreferencesOldApi();
} else {
getFragmentManager().beginTransaction().
replace(android.R.id.content, new GeneralPrefsFragment()).commit();
}
}
@SuppressWarnings("deprecation")
private void addPreferencesOldApi() {
addPreferencesFromResource(R.xml.general_preferences);
initPreferences((EditTextPreference) findPreference(KEY_DEVICE_NAME_PREFERENCE));
}
private void initPreferences(final EditTextPreference deviceNamePref) {
final SharedPreferences sharedPreferences=PreferenceManager.getDefaultSharedPreferences(this);
deviceNamePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newDeviceName) {
if (newDeviceName.toString().isEmpty()) {
Toast.makeText(
MainSettingsActivity.this,
getString(R.string.invalid_device_name),
Toast.LENGTH_SHORT).show();
return false;
}else{
Log.i("MainSettingsActivity", "New device name: " + newDeviceName);
deviceNamePref.setSummary(getString(
R.string.device_name_preference_summary,
newDeviceName.toString()));
//Broadcast the device information again since it has changed
BackgroundService.RunCommand(MainSettingsActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.onNetworkChange();
}
});
return true;
}
}
});
deviceNamePref.setSummary(getString(
R.string.device_name_preference_summary,
sharedPreferences.getString(KEY_DEVICE_NAME_PREFERENCE,"")));
}
/**
* Until now it sets only the default deviceName (if not already set).
* It's safe to call this multiple time because doesn't override any previous value.
* @param context the application context
*/
public static void initializeDeviceName(Context context){
// I could have used getDefaultSharedPreferences(context).contains but we need to check
// to checkAgainst empty String also.
String deviceName=PreferenceManager.getDefaultSharedPreferences(context).getString(
KEY_DEVICE_NAME_PREFERENCE,
"");
if(deviceName.isEmpty()){
Log.i("MainSettingsActivity", "New device name: " + deviceName);
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(
KEY_DEVICE_NAME_PREFERENCE,
DeviceHelper.getDeviceName()).commit();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.general_preferences);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity() != null) {
((MainSettingsActivity)getActivity()).initPreferences(
(EditTextPreference) findPreference(KEY_DEVICE_NAME_PREFERENCE));
}
}
}
}

View File

@@ -18,7 +18,7 @@ public class PairActivity extends ActionBarActivity {
private String deviceId;
private Device device = null;
private Device.PairingCallback pairingCallback = new Device.PairingCallback() {
private final Device.PairingCallback pairingCallback = new Device.PairingCallback() {
@Override
public void incomingRequest() {

View File

@@ -2,7 +2,6 @@ package org.kde.kdeconnect.UserInterface;
import android.content.Context;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
@@ -12,7 +11,7 @@ import java.util.ArrayList;
public class PreferenceListAdapter extends ArrayAdapter<Preference> {
private ArrayList<Preference> localList;
private final ArrayList<Preference> localList;
public PreferenceListAdapter(Context context, ArrayList<Preference> items) {
super(context,0, items);

View File

@@ -4,7 +4,6 @@ import android.app.ListActivity;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;

View File

@@ -24,6 +24,7 @@ import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect_tp.R;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
@@ -175,38 +176,62 @@ public class ShareToReceiver extends ActionBarActivity {
InputStream inputStream = cr.openInputStream(uri);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE);
String[] proj = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME };
Cursor cursor = managedQuery(uri, proj, null, null, null);
int size = -1;
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
cursor.moveToFirst();
size = cursor.getInt(column_index);
} catch(Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file size");
}
//Log.e("ShareToReceiver", "Size "+size);
np.setPayload(inputStream, size);
if (uri.getScheme().equals("file")) {
// file:// is a non media uri, so we cannot query the ContentProvider
np.set("filename", uri.getLastPathSegment());
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
cursor.moveToFirst();
String path = cursor.getString(column_index);
np.set("filename", Uri.parse(path).getLastPathSegment());
} catch(Exception _) {
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(column_index);
np.set("filename", name);
} catch (Exception e) {
size = (int)new File(uri.getPath()).length();
np.setPayload(inputStream, size);
} catch(Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file name");
Log.e("ShareToReceiver", "Could not obtain file size");
}
}else{
// Probably a content:// uri, so we query the Media content provider
Cursor cursor = null;
try {
String[] proj = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME };
cursor = getContentResolver().query(uri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
cursor.moveToFirst();
String path = cursor.getString(column_index);
np.set("filename", Uri.parse(path).getLastPathSegment());
np.set("size", (int)new File(path).length());
} catch(Exception _) {
Log.e("ShareToReceiver", "Could not resolve media to a file, trying to get info as media");
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(column_index);
np.set("filename", name);
} catch (Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file name");
}
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
cursor.moveToFirst();
//For some reason this size can differ from the actual file size!
size = cursor.getInt(column_index);
} catch(Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file size");
}
} finally {
cursor.close();
}
np.setPayload(inputStream, size);
}
device.sendPackage(np, new Device.SendPackageFinishedCallback() {
@@ -218,12 +243,13 @@ public class ShareToReceiver extends ActionBarActivity {
@Override
public void sendFailed() {
Log.e("ShareToReceiver", "Failed to send attachment");
Log.e("ShareToReceiver", "Failed to send file");
}
});
} catch (Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Failed to send attachment");
Log.e("ShareToReceiver", "Exception sending files");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mousepad_view">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/mousepad_info"
android:gravity="center"
style="@android:style/TextAppearance.Medium"
android:layout_centerInParent="true"
android:padding="12dip" />
</RelativeLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:baselineAligned="false"
android:layout_height="wrap_content"

View File

@@ -37,7 +37,7 @@
<LinearLayout
android:orientation="horizontal"
android:layout_width="200dip"
android:layout_height="70dip"
android:layout_height="50dip"
android:layout_gravity="center"
>
@@ -45,19 +45,36 @@
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/prev_button"
android:src="@android:drawable/ic_media_rew"
android:src="@android:drawable/ic_media_previous"
android:contentDescription="@string/mpris_previous"
android:layout_weight="0.5"
android:layout_weight="0.25"
/>
<ImageButton
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/rew_button"
android:src="@android:drawable/ic_media_rew"
android:contentDescription="@string/mpris_rew"
android:layout_weight="0.25"
/>
<ImageButton
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/ff_button"
android:src="@android:drawable/ic_media_ff"
android:contentDescription="@string/mpris_ff"
android:layout_weight="0.25"
/>
<ImageButton
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/next_button"
android:src="@android:drawable/ic_media_ff"
android:layout_gravity="center"
android:src="@android:drawable/ic_media_next"
android:contentDescription="@string/mpris_next"
android:layout_weight="0.5"
android:layout_weight="0.25"
/>
</LinearLayout>
@@ -77,7 +94,7 @@
android:layout_weight="1"
android:layout_gravity="left|center_vertical"
android:contentDescription="@string/mpris_volume"
android:src="@drawable/volume"
android:src="@drawable/ic_volume"
/>
@@ -107,4 +124,4 @@
android:src="@android:drawable/ic_media_stop"
android:layout_gravity="center"/>
-->
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,27 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto/android">
<item
android:id="@+id/menu_refresh"
android:icon="@drawable/ic_action_refresh"
android:orderInCategory="200"
kdeconnect:showAsAction="ifRoom"
android:title="@string/reconnect"
/>
<item
android:id="@+id/menu_progress"
android:orderInCategory="200"
android:visible="false"
kdeconnect:showAsAction="ifRoom"
kdeconnect:actionViewClass="android.widget.ProgressBar"
/>
<item
android:id="@+id/menu_settings"
android:icon="@drawable/ic_action_settings"
android:orderInCategory="300"
android:title="@string/settings"
kdeconnect:showAsAction="never"
/>
</menu>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto/android">
<item android:id="@+id/menu_right_click"
android:title="@string/right_click"
kdeconnect:showAsAction="never" />
<item android:id="@+id/menu_middle_click"
android:title="@string/middle_click"
kdeconnect:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,70 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Известяване за обаждания</string>
<string name="pref_plugin_telephony_desc">Изпращане на уведомления за СМС и обаждания</string>
<string name="pref_plugin_battery">Доклад за батерията</string>
<string name="pref_plugin_battery_desc">Периодично съобщаване за състоянието на батерията</string>
<string name="pref_plugin_sftp">Достъп до файловата система</string>
<string name="pref_plugin_sftp_desc">Позволява отдалечен достъп до файловата система на телефона</string>
<string name="pref_plugin_clipboard">Синхронизиране на буфера</string>
<string name="pref_plugin_clipboard_desc">Споделяне съдържанието на буфера</string>
<string name="pref_plugin_mousepad">Отдалечен контрол</string>
<string name="pref_plugin_mpris">Отдалечен мултимедиен контрол</string>
<string name="pref_plugin_mpris_desc">Управление на звук/видео от телефона</string>
<string name="pref_plugin_ping">Пинг</string>
<string name="pref_plugin_ping_desc">Изпращане и получаване на пинг</string>
<string name="pref_plugin_notifications">Синхронизиране на уведомленията</string>
<string name="pref_plugin_notifications_desc">Достъп до уведомленията от други устройства</string>
<string name="plugin_not_available">Тази функция не е налична във вашата версия на Андроид</string>
<string name="device_list_empty">Няма устройства</string>
<string name="ok">Добре</string>
<string name="cancel">Отказ</string>
<string name="open_settings">Отваряне на настройките</string>
<string name="no_permissions">Трябва да осигурите права за достъп до уведомленията</string>
<string name="send_ping">Изпращане на пинг</string>
<string name="open_mpris_controls">Отваряне на отдалечен контрол</string>
<string name="open_mousepad">Отваряне на отдалечен контрол</string>
<string name="category_connected_devices">Свързани устройства</string>
<string name="category_not_paired_devices">Несдвоени устройства</string>
<string name="category_remembered_devices">Запомнени устройства</string>
<string name="plugins_failed_to_load">Неуспешно зареждане на приставки (докоснете за подробности)</string>
<string name="device_menu_plugins">Изберете приставки</string>
<string name="device_menu_unpair">Разкачване</string>
<string name="device_not_reachable">Устройството е недостъпно</string>
<string name="unknown_device">Неизвестно устройство</string>
<string name="error_not_reachable">Устройството е недостъпно</string>
<string name="error_already_requested">Вече е заявено сдвояване</string>
<string name="error_already_paired">Устройството вече е сдвоено</string>
<string name="error_could_not_send_package">Пакетът не може да бъде изпратен</string>
<string name="error_timed_out">Просрочка</string>
<string name="error_canceled_by_user">Отхвърлена от потребителя</string>
<string name="error_canceled_by_other_peer">Отказана от другата страна</string>
<string name="error_invalid_key">Получен е неправилен ключ</string>
<string name="pair_requested">Заявено е сдвояване</string>
<string name="pairing_request_from">Заявено е сдвояване от %1s</string>
<string name="incoming_file_title">Заявено е сдвояване от %1s</string>
<string name="incoming_file_text">%s</string>
<string name="tap_to_answer">Докоснете за да отговорите</string>
<string name="reconnect">Ново свързване</string>
<string name="device_not_paired">Устройството не е сдвоено</string>
<string name="request_pairing">Заявка за сдвояване</string>
<string name="pairing_accept">Приемане</string>
<string name="pairing_reject">Отхвърляне</string>
<string name="device">Устройство</string>
<string name="pair_device">Сдвояване с устройството</string>
<string name="remote_control">Отдалечен контрол</string>
<string name="settings">Настройки на KDE Connect</string>
<string name="mpris_play">Възпроизвеждане</string>
<string name="mpris_previous">Предишен</string>
<string name="mpris_rew">Превъртане назад</string>
<string name="mpris_ff">Превъртане напред</string>
<string name="mpris_next">Следващ</string>
<string name="mpris_volume">Сила на звука</string>
<string name="share_to">Споделяне...</string>
<string name="protocol_version_older">Това устройство използва стара версия на протокола</string>
<string name="protocol_version_newer">Това устройство използва по-нова версия на протокола</string>
<string name="general_settings">Общи настройки</string>
<string name="device_name">Име на устройството</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Невалидно име на устройство</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_battery">Baterijski izvještaj</string>
<string name="pref_plugin_battery_desc">Periodično javi baterijski status</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Šalji i primaj ping-ove</string>
<string name="pref_plugin_notifications">Sinhronizovano obavještenje</string>
</resources>

View File

@@ -0,0 +1,81 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Notificador de telefonia</string>
<string name="pref_plugin_telephony_desc">Envia notificacions pels SMS i les trucades</string>
<string name="pref_plugin_battery">Informe de la bateria</string>
<string name="pref_plugin_battery_desc">Informa periòdicament sobre l\'estat de la bateria</string>
<string name="pref_plugin_sftp">Exposa el sistema de fitxers</string>
<string name="pref_plugin_sftp_desc">Permet navegar de forma remota pel sistema de fitxers del telèfon</string>
<string name="pref_plugin_clipboard">Sincronitza el porta-retalls</string>
<string name="pref_plugin_clipboard_desc">Comparteix el contingut del porta-retalls</string>
<string name="pref_plugin_mousepad">Comandament a distància del ratolí</string>
<string name="pref_plugin_mousepad_desc">Controla el vostre ratolí de forma remota</string>
<string name="pref_plugin_mpris">Comandament a distància 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_ping">Ping</string>
<string name="pref_plugin_ping_desc">Envia i rep els pings</string>
<string name="pref_plugin_notifications">Sincronitza les notificacions</string>
<string name="pref_plugin_notifications_desc">Accedeix a les vostres notificacions des d\'altres dispositius</string>
<string name="pref_plugin_sharereceiver">Receptor de compartits</string>
<string name="pref_plugin_sharereceiver_desc">Desa els fitxers entrants a l\'emmagatzematge del telèfon</string>
<string name="plugin_not_available">Aquesta característica no està disponible en la vostra versió d\'Android</string>
<string name="device_list_empty">No hi ha cap dispositiu</string>
<string name="ok">D\'acord</string>
<string name="cancel">Cancel·la</string>
<string name="open_settings">Obre l\'arranjament</string>
<string name="no_permissions">Us caldrà concedir permís per accedir a les notificacions</string>
<string name="send_ping">Envia un ping</string>
<string name="open_mpris_controls">Obre el comandament a distància</string>
<string name="open_mousepad">Obre el control pel plafó tàctil</string>
<string name="mousepad_info">Moveu un dit per la pantalla per a moure el cursor del ratolí</string>
<string name="category_connected_devices">Dispositius connectats</string>
<string name="category_not_paired_devices">Dispositius sense aparellar</string>
<string name="category_remembered_devices">Dispositius recordats</string>
<string name="plugins_failed_to_load">Els connectors han fallat en carregar-los (puntegeu per a més informació):</string>
<string name="device_menu_plugins">Selecció dels connectors</string>
<string name="device_menu_unpair">Desparella</string>
<string name="device_not_reachable">No s\'ha pogut accedir al dispositiu parell</string>
<string name="unknown_device">Dispositiu desconegut</string>
<string name="error_not_reachable">No es pot accedir al dispositiu</string>
<string name="error_already_requested">Ja s\'ha demanat aparellar</string>
<string name="error_already_paired">El dispositiu ja està aparellat</string>
<string name="error_could_not_send_package">No s\'ha pogut enviar el paquet</string>
<string name="error_timed_out">Ha excedit el temps</string>
<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="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>
<string name="received_url_text">Puntegeu per obrir «%1s»</string>
<string name="incoming_file_title">Fitxer entrant des de %1s</string>
<string name="incoming_file_text">%1s</string>
<string name="received_file_title">Fitxer rebut des de %1s</string>
<string name="received_file_text">Puntegeu per obrir «%1s»</string>
<string name="tap_to_answer">Puntegeu per a respondre</string>
<string name="reconnect">Reconnecta</string>
<string name="right_click">Envia un clic del botó dret</string>
<string name="middle_click">Envia un clic del botó del mig</string>
<string name="device_not_paired">El dispositiu no està aparellat</string>
<string name="request_pairing">Demana aparellar</string>
<string name="pairing_accept">Accepta</string>
<string name="pairing_reject">Rebutja</string>
<string name="device">Dispositiu</string>
<string name="pair_device">Dispositiu parell</string>
<string name="remote_control">Comandament a distància</string>
<string name="settings">Arranjament del KDE Connect</string>
<string name="mpris_play">Reprodueix</string>
<string name="mpris_previous">Anterior</string>
<string name="mpris_rew">Rebobina</string>
<string name="mpris_ff">Avanç ràpid</string>
<string name="mpris_next">Següent</string>
<string name="mpris_volume">Volum</string>
<string name="share_to">Comparteix amb...</string>
<string name="protocol_version_older">Aquest dispositiu usa una versió antiga del protocol</string>
<string name="protocol_version_newer">Aquest dispositiu usa una versió nova del protocol</string>
<string name="general_settings">Arranjament general</string>
<string name="device_name">Nom del dispositiu</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">El nom del dispositiu no és vàlid</string>
<string name="shareplugin_text_saved">S\'ha rebut text i s\'ha desat al porta-retalls</string>
</resources>

View File

@@ -0,0 +1,81 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Oznamování telefonie</string>
<string name="pref_plugin_telephony_desc">Posílat upozorňování na SMS a hovory</string>
<string name="pref_plugin_battery">Hlášení baterie</string>
<string name="pref_plugin_battery_desc">Periodicky hlásit stav baterky</string>
<string name="pref_plugin_sftp">Přístup k souborovému systému</string>
<string name="pref_plugin_sftp_desc">Umožní vám vzdáleně prohlížet souborový systém telefonu</string>
<string name="pref_plugin_clipboard">Synchronizace schránky</string>
<string name="pref_plugin_clipboard_desc">Sdílet obsah schránky</string>
<string name="pref_plugin_mousepad">Vzdálené ovládání myši</string>
<string name="pref_plugin_mousepad_desc">Ovládejte svou myš vzdáleně</string>
<string name="pref_plugin_mpris">Vzdálené 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_ping">Ping</string>
<string name="pref_plugin_ping_desc">Posílat a přijímat ping</string>
<string name="pref_plugin_notifications">Synchronizace hlášení</string>
<string name="pref_plugin_notifications_desc">Zpřístupněte si upozornění z jiných zařízení</string>
<string name="pref_plugin_sharereceiver">Sdílet příjemce</string>
<string name="pref_plugin_sharereceiver_desc">Ukládat příchozí soubory do úložiště telefonu</string>
<string name="plugin_not_available">Tato vlastnost není pro vaši verzi Androidu platná</string>
<string name="device_list_empty">Žádná zařízení</string>
<string name="ok">OK</string>
<string name="cancel">Zrušit</string>
<string name="open_settings">Otevřít nastavení</string>
<string name="no_permissions">Pro zpřístupnění upozornění potřebujete oprávnění</string>
<string name="send_ping">Poslat ping</string>
<string name="open_mpris_controls">Otevřít vzdálené ovládání</string>
<string name="open_mousepad">Otevřít dotykové ovládání</string>
<string name="mousepad_info">Pohybujte prstem po monitoru pro pohybování kurzorem myši</string>
<string name="category_connected_devices">Připojená zařízení</string>
<string name="category_not_paired_devices">Nespárovaná zařízení</string>
<string name="category_remembered_devices">Zapamatovaná zařízení</string>
<string name="plugins_failed_to_load">Moduly nebyly načteny (ťukněte pro více informací):</string>
<string name="device_menu_plugins">Vyberte moduly</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="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>
<string name="error_already_paired">Zařízení je již spárované</string>
<string name="error_could_not_send_package">Balíček nelze poslat</string>
<string name="error_timed_out">Čas vypršel</string>
<string name="error_canceled_by_user">Přerušeno uživatelem</string>
<string name="error_canceled_by_other_peer">Přerušeno druhým uživatelem</string>
<string name="error_invalid_key">Byl přijat neplatný klíč</string>
<string name="pair_requested">Bylo vyžádáno párování</string>
<string name="pairing_request_from">Požadavek o párování z %1s</string>
<string name="received_url_title">Přijat odkaz od %1s</string>
<string name="received_url_text">Ťukněte pro otevření \'%1s\'</string>
<string name="incoming_file_title">Příchozí soubor od %1s</string>
<string name="incoming_file_text">%1s</string>
<string name="received_file_title">Přijat soubor od %1s</string>
<string name="received_file_text">Ťukněte pro otevření \'%1s\'</string>
<string name="tap_to_answer">Ťukněte pro odpovězení</string>
<string name="reconnect">Znovu připojit</string>
<string name="right_click">Poslat kliknutí pravým tlačítkem</string>
<string name="middle_click">Poslat kliknutí prostředním tlačítkem</string>
<string name="device_not_paired">Zařízení není spárované</string>
<string name="request_pairing">Vyžádat párování</string>
<string name="pairing_accept">Přijmout</string>
<string name="pairing_reject">Odmítnout</string>
<string name="device">Zařízení</string>
<string name="pair_device">Spárovat zařízení</string>
<string name="remote_control">Vzdálené ovládání</string>
<string name="settings">Nastavení KDE Connect</string>
<string name="mpris_play">Přehrát</string>
<string name="mpris_previous">Předchozí</string>
<string name="mpris_rew">Přetočit zpět</string>
<string name="mpris_ff">Rychle vpřed</string>
<string name="mpris_next">Následující</string>
<string name="mpris_volume">Hlasitost</string>
<string name="share_to">Sdílet s...</string>
<string name="protocol_version_older">Toto zařízení používá starou verzi protokolu</string>
<string name="protocol_version_newer">Toto zařízení používá novější verzi protokolu</string>
<string name="general_settings">Obecná nastavení</string>
<string name="device_name">Název zařízení</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Neplatný název zařízení</string>
<string name="shareplugin_text_saved">Přijatý text byl uložen do schránky</string>
</resources>

View File

@@ -0,0 +1,72 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Telefonibekendtgørelse</string>
<string name="pref_plugin_telephony_desc">Send bekendtgørelser om SMS\'er og opkald</string>
<string name="pref_plugin_battery">Batterirapport</string>
<string name="pref_plugin_battery_desc">Rapportér batteristatus periodisk</string>
<string name="pref_plugin_sftp">Filsystem-expose</string>
<string name="pref_plugin_sftp_desc">Muliggør at gennemsøge telefonens filsystem eksternt</string>
<string name="pref_plugin_clipboard">Synk. af udklipsholder</string>
<string name="pref_plugin_clipboard_desc">Del indholdet af udklipsholderen</string>
<string name="pref_plugin_mousepad">Fjernbetjening af mus</string>
<string name="pref_plugin_mousepad_desc">Styrer din mus på afstand</string>
<string name="pref_plugin_mpris">Fjernbetjening af multimedie</string>
<string name="pref_plugin_mpris_desc">Styr lyd og video fra din telefon</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Send og modtag ping</string>
<string name="pref_plugin_notifications">Synk. af bekendtgørelser</string>
<string name="pref_plugin_notifications_desc">Tilgå dine bekendtgørelser fra andre enheder</string>
<string name="plugin_not_available">Denne funktion er ikke tilgængelig i din Android-version</string>
<string name="device_list_empty">Ingen enheder</string>
<string name="ok">O.k.</string>
<string name="cancel">Annullér</string>
<string name="open_settings">Åbn indstillinger</string>
<string name="no_permissions">Du skal give tilladelse for at tilgå bekendtgørelser</string>
<string name="send_ping">Send ping</string>
<string name="open_mpris_controls">Åbn fjernbetjening</string>
<string name="open_mousepad">Åbn museplade-kontrol</string>
<string name="mousepad_info">Bevæg din tommelfinger på skærmen for at flytte musemarkøren</string>
<string name="category_connected_devices">Forbundne enheder</string>
<string name="category_not_paired_devices">Ikke parrede enheder</string>
<string name="category_remembered_devices">Huskede enheder</string>
<string name="plugins_failed_to_load">Plugins kunne ikke indlæses (tap for mere info):</string>
<string name="device_menu_plugins">Vælg plugins</string>
<string name="device_menu_unpair">Fjern parring</string>
<string name="device_not_reachable">Den parrede enhed kan ikke tilgås</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>
<string name="error_already_paired">Enhed allerede parret</string>
<string name="error_could_not_send_package">Kunne ikke sende pakke</string>
<string name="error_timed_out">Tidsudløb</string>
<string name="error_canceled_by_user">Annulleret af brugeren</string>
<string name="error_canceled_by_other_peer">Annulleret af modpart</string>
<string name="error_invalid_key">Ugyldige nøgle modtaget</string>
<string name="pair_requested">Anmodet om parring</string>
<string name="pairing_request_from">Parringsanmodning fra %1s</string>
<string name="incoming_file_title">Parringsanmodning fra %1s</string>
<string name="incoming_file_text">%s</string>
<string name="tap_to_answer">Tap for at svare</string>
<string name="reconnect">Forbind igen</string>
<string name="device_not_paired">Enhed ikke parret</string>
<string name="request_pairing">Anmod om parring</string>
<string name="pairing_accept">Acceptér</string>
<string name="pairing_reject">Afvis</string>
<string name="device">Enhed</string>
<string name="pair_device">Par med enhed</string>
<string name="remote_control">Fjernbetjening</string>
<string name="settings">Indstilling af KDE Connect</string>
<string name="mpris_play">Afspil</string>
<string name="mpris_previous">Forrige</string>
<string name="mpris_rew">Spol tilbage</string>
<string name="mpris_ff">Spol frem</string>
<string name="mpris_next">Næste</string>
<string name="mpris_volume">Lydstyrke</string>
<string name="share_to">Del på...</string>
<string name="protocol_version_older">Denne enhed bruger en gammel protokolversion</string>
<string name="protocol_version_newer">Denne enhed bruger en nyere protokolversion</string>
<string name="general_settings">Generelle indstillinger</string>
<string name="device_name">Enhedsnavn</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Ugyldigt enhedsnavn</string>
</resources>

View File

@@ -0,0 +1,70 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Telefon-Benachrichtigung</string>
<string name="pref_plugin_telephony_desc">Benachrichtigungen über SMS und Anrufe senden</string>
<string name="pref_plugin_battery">Akkubericht</string>
<string name="pref_plugin_battery_desc">Akkustatus periodisch berichten</string>
<string name="pref_plugin_sftp">Dateisystem zeigen</string>
<string name="pref_plugin_sftp_desc">Erlaubt das Browsen des Dateisystems auf dem entfernten Handy</string>
<string name="pref_plugin_clipboard">Abgleich der Zwischenablage</string>
<string name="pref_plugin_clipboard_desc">Inhalt der Zwischenablage freigeben</string>
<string name="pref_plugin_mousepad">Fernbedienung</string>
<string name="pref_plugin_mpris">Multimedia-Fernbedienungen</string>
<string name="pref_plugin_mpris_desc">Audio und Video mit Ihrem Telefon steuern</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>
<string name="pref_plugin_notifications_desc">Zugriff auf Ihre Benachrichtigungen von anderen Geräten</string>
<string name="plugin_not_available">Diese Funktion ist in Ihrer Android-Version nicht verfügbar</string>
<string name="device_list_empty">Keine Geräte</string>
<string name="ok">OK</string>
<string name="cancel">Abbrechen</string>
<string name="open_settings">Einstellungen öffnen</string>
<string name="no_permissions">Sie müssen die Erlaubnis zum Zugriff auf Benachrichtigungen erteilen</string>
<string name="send_ping">Ping senden</string>
<string name="open_mpris_controls">Fernbedienung öffnen</string>
<string name="open_mousepad">Fernbedienung öffnen</string>
<string name="category_connected_devices">Verbundene Geräte</string>
<string name="category_not_paired_devices">Keine angeschlossenen Geräte</string>
<string name="category_remembered_devices">Gemerkte Geräte</string>
<string name="plugins_failed_to_load">Laden der Module fehlgeschlagen, tippen Sie für weitere Details:</string>
<string name="device_menu_plugins">Module auswählen</string>
<string name="device_menu_unpair">Verbindung trennen</string>
<string name="device_not_reachable">Das Gerät ist nicht erreichbar</string>
<string name="unknown_device">Unbekanntes Gerät</string>
<string name="error_not_reachable">Das Gerät ist nicht erreichbar</string>
<string name="error_already_requested">Die Verbindung wurde bereits angefragt</string>
<string name="error_already_paired">Das Gerät ist bereits verbunden</string>
<string name="error_could_not_send_package">Das Paket kann nicht gesendet werden</string>
<string name="error_timed_out">Zeitüberschreitung</string>
<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="pair_requested">Verbindung angefordert</string>
<string name="pairing_request_from">Verbindungsanfrage von %1s</string>
<string name="incoming_file_title">Verbindungsanfrage von %1s</string>
<string name="incoming_file_text">%s</string>
<string name="tap_to_answer">Tippen zum Antworten</string>
<string name="reconnect">Erneut verbinden</string>
<string name="device_not_paired">Das Gerät ist nicht verbunden</string>
<string name="request_pairing">Verbindung angefordert</string>
<string name="pairing_accept">Annehmen</string>
<string name="pairing_reject">Ablehnen</string>
<string name="device">Gerät</string>
<string name="pair_device">Gerät verbinden</string>
<string name="remote_control">Fernbedienung</string>
<string name="settings">KDE-Verbindungseinstellungen</string>
<string name="mpris_play">Wiedergabe</string>
<string name="mpris_previous">Zurück</string>
<string name="mpris_rew">Schneller Rücklauf</string>
<string name="mpris_ff">Vorlauf</string>
<string name="mpris_next">Weiter</string>
<string name="mpris_volume">Lautstärke</string>
<string name="share_to">Freigeben für ...</string>
<string name="protocol_version_older">Dieses Gerät verwendet ein alte Protokollversion</string>
<string name="protocol_version_newer">Dieses Gerät verwendet ein neuere Protokollversion</string>
<string name="general_settings">Allgemeine Einstellungen</string>
<string name="device_name">Gerätename</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Ungültiger Gerätename</string>
</resources>

View File

@@ -0,0 +1,70 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Notificador de telefonía</string>
<string name="pref_plugin_telephony_desc">Enviar notificaciones de SMS y llamadas</string>
<string name="pref_plugin_battery">Informe de la batería</string>
<string name="pref_plugin_battery_desc">Informar periódicamente del estado de la batería</string>
<string name="pref_plugin_sftp">Revelar el sistema de archivos</string>
<string name="pref_plugin_sftp_desc">Permite explorar de forma remota el sistema de archivos del teléfono</string>
<string name="pref_plugin_clipboard">Sincronización del portapapeles</string>
<string name="pref_plugin_clipboard_desc">Compartir el contenido del portapapeles</string>
<string name="pref_plugin_mousepad">Control remoto</string>
<string name="pref_plugin_mpris">Controles remotos multimedia</string>
<string name="pref_plugin_mpris_desc">Controlar audio y vídeo desde el teléfono</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Enviar y recibir pings</string>
<string name="pref_plugin_notifications">Sincronizar notificaciones</string>
<string name="pref_plugin_notifications_desc">Acceder a las notificaciones desde otros dispositivos</string>
<string name="plugin_not_available">Esta función no está disponible en su versión de Android</string>
<string name="device_list_empty">Ningún dispositivo</string>
<string name="ok">Aceptar</string>
<string name="cancel">Cancelar</string>
<string name="open_settings">Abrir preferencias</string>
<string name="no_permissions">Debe otorgar permiso para acceder a las notificaciones</string>
<string name="send_ping">Enviar ping</string>
<string name="open_mpris_controls">Abrir control remoto</string>
<string name="open_mousepad">Abrir control remoto</string>
<string name="category_connected_devices">Dispositivos conectados</string>
<string name="category_not_paired_devices">Dispositivos no vinculados</string>
<string name="category_remembered_devices">Dispositivos recordados</string>
<string name="plugins_failed_to_load">Complementos cuya carga ha fallado (toque para más información):</string>
<string name="device_menu_plugins">Seleccionar complementos</string>
<string name="device_menu_unpair">Desvincular</string>
<string name="device_not_reachable">No se encuentra el dispositivo aparejado</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>
<string name="error_already_paired">Dispositivo ya vinculado</string>
<string name="error_could_not_send_package">No se puede enviar el paquete</string>
<string name="error_timed_out">Se ha agotado el tiempo</string>
<string name="error_canceled_by_user">Cancelado por el usuario</string>
<string name="error_canceled_by_other_peer">Cancelado por la otra parte</string>
<string name="error_invalid_key">Se ha recibido una clave no valida</string>
<string name="pair_requested">Vinculación solicitada</string>
<string name="pairing_request_from">Solicitud de vinculación de %1s</string>
<string name="incoming_file_title">Solicitud de vinculación de %1s</string>
<string name="incoming_file_text">%s</string>
<string name="tap_to_answer">Toque para responder</string>
<string name="reconnect">Reconectar</string>
<string name="device_not_paired">Dispositivo no vinculado</string>
<string name="request_pairing">Solicitar vinculación</string>
<string name="pairing_accept">Aceptar</string>
<string name="pairing_reject">Rechazar</string>
<string name="device">Dispositivo</string>
<string name="pair_device">Vincular dispositivo</string>
<string name="remote_control">Control remoto</string>
<string name="settings">Preferencias de KDE Connect</string>
<string name="mpris_play">Reproducir</string>
<string name="mpris_previous">Anterior</string>
<string name="mpris_rew">Rebobinar</string>
<string name="mpris_ff">Avance rápido</string>
<string name="mpris_next">Siguiente</string>
<string name="mpris_volume">Volumen</string>
<string name="share_to">Compartir con...</string>
<string name="protocol_version_older">Este dispositivo usa una versión antigua del protocolo</string>
<string name="protocol_version_newer">Este dispositivo usa una versión más reciente del protocolo</string>
<string name="general_settings">Preferencias generales</string>
<string name="device_name">Nombre de dispositivo</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Nombre de dispositivo no valido</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More