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

Compare commits

...

258 Commits
v0.9g ... v1.0

Author SHA1 Message Date
Albert Vaca
b0b67f2059 Added fixme 2016-06-03 01:35:45 +02:00
Albert Vaca
53ad86fe9a Do not show red messages to everybody (we will release Android before KDE) 2016-06-03 01:35:15 +02:00
Albert Vaca
39107400d4 Changed runcommand description 2016-06-03 01:12:47 +02:00
Albert Vaca
3f20af445a Renaming a package type was a bad call 2016-06-03 00:46:18 +02:00
Albert Vaca
e433ad8bce These logs are useless if we have no capabilities 2016-06-02 16:14:52 +02:00
Albert Vaca
53ca2a392c Hack got hackier 2016-06-02 15:57:12 +02:00
Albert Vaca
b7d9cb5d81 Using capabilities, packages no longer need to validate received packages 2016-06-02 15:35:44 +02:00
Albert Vaca
05e031d41f Changed to match the kdeconnect-kde side 2016-06-02 15:23:49 +02:00
Albert Vaca
d7956650da Log when sending a package type that no plugin has declared 2016-06-02 15:23:34 +02:00
Albert Vaca
43467aedd8 Hack to make packet types match those of older versions 2016-06-02 15:22:40 +02:00
Albert Vaca
541cd97c71 If capabilities are not present, iterate all plugins 2016-06-02 15:22:21 +02:00
Albert Vaca
7759152eeb Do not swallow packets silently 2016-06-02 13:56:46 +02:00
Albert Vaca
305d496bf5 Mute call button was not working on Android M because of a deprecated API 2016-06-02 13:26:19 +02:00
Albert Vaca
01b5c3e2a1 Be less verbose 2016-06-02 13:25:55 +02:00
Albert Vaca
a26d6a94f9 Increased version number for release candidate. 2016-06-02 12:37:20 +02:00
Albert Vaca
9dc8db00b5 Disabled SMS plugin so we don't require new permissions in this version 2016-06-02 12:36:50 +02:00
Albert Vaca
5ef5161378 Typo 2016-06-02 12:30:40 +02:00
Albert Vaca
0897f25a6f Less verbose logging of unmatched capabilities 2016-06-02 12:28:33 +02:00
Albert Vaca
394e530545 Wait a bit before forgetting about an old channel.
We might still receive a packet sent before the replacement.
2016-06-02 12:24:22 +02:00
Albert Vaca
ebbf5d165c Cosmetic changes to variable names 2016-06-02 12:20:04 +02:00
Albert Vaca
a0d2a61ce9 Merge branch 'master' into sslrefactor 2016-06-01 13:21:55 +02:00
Albert Vaca
2350e86023 Using the default theme in findmyphone 2016-06-01 13:21:30 +02:00
Albert Vaca
2552663742 Checking for null didn't work, just using the ringtone now. 2016-06-01 13:21:17 +02:00
Albert Vaca
d729eed1cd Merge branch 'master' into sslrefactor
# Conflicts:
#	src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java
#	src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
2016-06-01 12:58:10 +02:00
Albert Vaca
284ef5e571 TYPE_ALARM can return null. Adding a fallback. 2016-06-01 12:56:07 +02:00
Albert Vaca
0c98964f16 Input plugin only sends requests 2016-06-01 12:07:22 +02:00
Albert Vaca
6783ce2f1b Removed old log 2016-05-31 21:02:48 +02:00
Albert Vaca
6f6caa0e8d Log if plugins receive wrong packets 2016-05-31 21:02:35 +02:00
Albert Vaca
614505518b Fixed capabilities 2016-05-31 21:02:17 +02:00
Albert Vaca
240fa34833 Do not use encryption if the protocol is 6 2016-05-31 21:00:18 +02:00
Albert Vaca
6ecfc5071e Less alarming notification icon 2016-05-31 19:04:09 +02:00
Albert Vaca
d539cd704b Since we renamed the capabilities, disable them on old clients
We don't want old kdeconnects to try to use the renamed capabilities.
2016-05-31 17:23:55 +02:00
Albert Vaca
d253fcfd0b Refactor capabilities together with Aleix
He is committing a similar patch to kdeconnect-kde
2016-05-31 17:19:39 +02:00
Albert Vaca
01b1e6ee4a Added missing shadow under the main activity's toolbar 2016-05-25 14:21:56 -06:00
Albert Vaca
969249e6d7 Added warning icon to the "not reachable" screen.
This way it doesn't look identical to the touchpad activity.
2016-05-25 14:01:30 -06:00
Albert Vaca
4f70c42ce5 Deleted stray file 2016-05-25 14:01:24 -06:00
Albert Vaca
4a612fd073 Removing log message 2016-05-25 10:57:50 -06:00
Albert Vaca
108f9e911d Typo 2016-05-25 10:57:27 -06:00
Albert Vaca
ddef90b985 Added some comments in build.gradle 2016-05-19 09:38:24 -07:00
Albert Vaca
e96f520829 Less and more useful logging in LanBackend 2016-05-19 09:29:17 -07:00
Albert Vaca
f9aeca8ef9 (un)serialize will now throw instead of failing silently 2016-05-19 09:28:15 -07:00
Albert Vaca
65c3dc4570 Reverting 6ed3a18
sshd > 0.8 seems to fail with "Could not find a valid sshd io provider"
2016-05-19 08:45:24 -07:00
l10n daemon script
aee22b5d15 SVN_SILENT made messages (after extraction) 2016-05-13 07:35:56 +00:00
l10n daemon script
2759da8d64 SVN_SILENT made messages (after extraction) 2016-05-08 07:17:03 +00:00
l10n daemon script
f615e95eeb SVN_SILENT made messages (after extraction) 2016-04-09 07:37:58 +00:00
l10n daemon script
e14da0d544 SVN_SILENT made messages (after extraction) 2016-04-04 08:20:21 +00:00
Albert Vaca
c01892f113 Fixed warnings 2016-03-15 13:54:44 -07:00
Albert Vaca
e39d5cc8ca The timer should remove the blacklisting, not add it again
Also added some logging and get the device id only once
2016-03-15 13:43:51 -07:00
Albert Vaca
45aa9d151f Compare against version 6, not current version 2016-03-15 13:37:23 -07:00
Albert Vaca
00715f3b6e Reusing provider 2016-03-15 12:55:25 -07:00
l10n daemon script
6e95e20521 SVN_SILENT made messages (after extraction) 2016-03-15 08:55:38 +00:00
Albert Vaca
2c513b598a Cleanup 2016-03-08 07:31:04 -08:00
Albert Vaca
fd7d39e6ac Added some proguard rules suggested by proguard itself when running 2016-03-07 15:49:45 -08:00
Albert Vaca
98914ce182 Fixed build
Somehow we need multidexing now, probably because when we build in debug
without proguard stripping unused code, we reach the max method count.
2016-03-07 15:49:30 -08:00
Albert Vaca
36e8d14973 Hack to fix "low battery" notification spam
When two phones were paired together and notification sync enabled, one
phone was receiving the notification again and again from the other, which
made obvious that it was being sent over and over.
2016-03-07 15:46:43 -08:00
Àlex Fiestas
17d5eeade0 Add fallback in case the contact does not have a name
Basically set the phoneNumber as name as it was before this change.

REVIEW: 127297
2016-03-07 11:38:36 +01:00
Àlex Fiestas
76eb88b51f Removed unused variable from ContactsHelper 2016-03-07 11:37:54 +01:00
Àlex Fiestas
07a9319c30 Also send the contact photo when a SMS is received
Pretty much like the phone call case.
2016-03-06 21:58:01 +01:00
Àlex Fiestas
c87b09c5f0 If the contact has a photo, include it in the packet
Basically adds as base64 in the packet the contact Photo. We should not
have to worry about the size of it since it is always 96x96 and android
takes care of that.
2016-03-06 21:58:01 +01:00
Àlex Fiestas
708bcf9928 Adapt phoneNumberLookup to return more information
It returns now photoID plus the name and it can be easily expanded in
the future if required.
2016-03-06 21:58:01 +01:00
Albert Vaca
b45a15c822 Made logs different so we can debug 2016-03-04 08:41:53 -08:00
Albert Vaca
0eba461654 Moved all the bouncy(spongy)castle code to the SslHelper class 2016-03-03 15:42:39 -08:00
Albert Vaca
7813b7309d Fixme 2016-03-03 15:41:44 -08:00
Albert Vaca
87e214761f Made sshd use spongycastle instead of bouncycastle
We have to add the DHG14 key exchange factory by hand because when calling
setUpDefaultServer with Bouncy Castle disabled, it assumes DHG14 is not
supported (but it is, because we are adding spongycastle).
2016-03-03 12:22:22 -08:00
Albert Vaca
6ed3a18f53 Upgraded sshd to 0.9
We can't upgrade it to 1.0 or 1.1 because these need java.nio.file, which
is standard java 7 but not present in Android.
2016-03-03 11:53:34 -08:00
Albert Vaca
db7e40fb35 Made proguard not keep **, but just the classes that we need
Disabled obfuscation, so stack traces are still useful
2016-03-03 11:37:36 -08:00
Albert Vaca
501ac90379 Java 7 is supported from API8, so why not enabling it? 2016-03-03 11:16:26 -08:00
Albert Vaca
4d553518f6 BouncyCastle doesn't work on Android 2.3, but spongy does
Now we have to make sshd work with spongy
2016-03-03 11:11:39 -08:00
Albert Vaca
9a2167774b Attribute not needed 2016-03-02 15:51:43 -08:00
Albert Vaca
22edb24230 Removed unused var. Fixed comments. Fixed typo. 2016-03-02 15:25:10 -08:00
Albert Vaca
f13a7148fc Don't complain about the public key we don't use 2016-03-02 11:40:10 -08:00
Albert Vaca
c0d21e986c Re-use existing link instead of creating a new one 2016-03-02 11:39:49 -08:00
Albert Vaca
baee1771f5 We don't need the chanel id to chanel map. 2016-03-02 10:40:17 -08:00
Albert Vaca
910af86c31 Updated dependencies 2016-03-02 10:39:23 -08:00
l10n daemon script
9f2999de23 SVN_SILENT made messages (after extraction) 2016-02-24 07:04:49 +00:00
Albert Vaca
acb2888bde Fixed test imports 2016-02-17 08:45:13 -08:00
Albert Vaca
06c872fddc LinearLayout is more performant than RelativeLayout 2016-02-17 08:45:03 -08:00
Albert Vaca
158931f766 Make drawer display under the system notifications bar 2016-02-17 08:44:41 -08:00
Albert Vaca
deac88031b Fixed drawer background not being the same color as the items background 2016-02-17 08:43:09 -08:00
Albert Vaca
f4c221cf66 Re-using a single SecureRandom, as it's expensive to initialize 2016-02-17 04:48:19 -08:00
Albert Vaca
521a27c09d Using longer and mixed case passwords 2016-02-17 04:48:01 -08:00
Albert Vaca
89a65ab3e2 Specify the TLS version we want
We need v1 to support Androids before API16
2016-02-17 04:07:12 -08:00
Albert Vaca
abd91b7d6c Sftp can now work with passwords when the public key is not available 2016-02-17 04:06:35 -08:00
l10n daemon script
79f096c09e SVN_SILENT made messages (after extraction) 2016-02-16 08:43:40 +00:00
l10n daemon script
9d0ef3ddf9 SVN_SILENT made messages (after extraction) 2016-02-13 07:27:17 +00:00
Albert Vaca
8c0fe406ec Merge branch 'master' into sslrefactor 2016-02-12 08:38:59 -08:00
Albert Vaca
7047622a62 Removing log message 2016-02-12 08:38:42 -08:00
Albert Vaca
98ef6aec8c Handle the case where there is no publicKey stored (for protocol v6) 2016-02-12 08:37:47 -08:00
Albert Vaca
c927af4bf3 Added newlines in encryption info 2016-02-12 08:37:18 -08:00
Albert Vaca
88b7c9898f Making sure proguard is not removing anything from bouncy/spongycastle
Some classes are only loaded dynamically and proguard would not see it.
2016-02-12 08:36:58 -08:00
Albert Vaca
62742ff8ae Updated netty 2016-02-12 08:34:27 -08:00
l10n daemon script
7f0ee677bd SVN_SILENT made messages (after extraction) 2016-02-12 07:23:39 +00:00
l10n daemon script
9b1f866cb0 SVN_SILENT made messages (after extraction) 2016-02-08 10:01:32 +00:00
l10n daemon script
353dc12165 SVN_SILENT made messages (after extraction) 2016-02-05 07:07:20 +00:00
l10n daemon script
5588c83909 SVN_SILENT made messages (after extraction) 2016-01-31 07:27:53 +00:00
l10n daemon script
b39433f237 SVN_SILENT made messages (after extraction) 2016-01-26 08:16:21 +00:00
l10n daemon script
612b019856 SVN_SILENT made messages (after extraction) 2016-01-25 09:01:01 +00:00
l10n daemon script
721856ebb6 SVN_SILENT made messages (after extraction) 2016-01-24 07:21:55 +00:00
l10n daemon script
2bd4d16361 SVN_SILENT made messages (after extraction) 2016-01-23 08:18:39 +00:00
Imran Tatriev
e3c5ac9228 Adjust the touchpad sensitivity 2016-01-21 16:39:31 +06:00
l10n daemon script
a75db516ec SVN_SILENT made messages (after extraction) 2016-01-20 08:01:32 +00:00
l10n daemon script
cf82127144 SVN_SILENT made messages (after extraction) 2016-01-19 08:50:26 +00:00
Albert Vaca
8a0e4caaff Fixed crash 2016-01-18 02:42:50 -08:00
Albert Vaca
c647cbd93c Made MPRIS interface wider
BUG: 346120
2016-01-18 02:42:50 -08:00
Albert Vaca
55871b6523 Bumped version number to release 2016-01-18 02:42:50 -08:00
Albert Vaca
8134c39a6d Android doesn't support languages with an @, hack to fix build 2016-01-18 02:42:50 -08:00
l10n daemon script
87c0a9f98e SVN_SILENT made messages (after extraction) 2016-01-18 02:42:06 -08:00
l10n daemon script
226e934d0f SVN_SILENT made messages (after extraction) 2016-01-18 02:42:06 -08:00
l10n daemon script
cfed03881b SVN_SILENT made messages (after extraction) 2016-01-18 09:12:23 +00:00
Albert Vaca
f618e8e670 Merge branch 'master' into sslrefactor 2016-01-17 10:36:27 -08:00
Albert Vaca
fe7b03830e Android doesn't support languages with an @, hack to fix build 2016-01-17 10:33:09 -08:00
Mikhail Ivchenko
caa491d1e7 Make a now playing label scroll horizontally
REVIEW: 126739
2016-01-17 22:22:13 +04:00
l10n daemon script
fba3e75cec SVN_SILENT made messages (after extraction) 2016-01-17 07:22:57 +00:00
Albert Astals Cid
71810c82d3 Disable colored output
Messes up scripty output that ends up in a file
2016-01-16 12:25:45 +01:00
l10n daemon script
7755772f5b SVN_SILENT made messages (after extraction) 2016-01-16 07:47:36 +00:00
Albert Vaca
53096e39eb Ignore if PublicKey is not present
It is not sent anymore while pairing as of protocol version 6
2016-01-15 09:21:51 -08:00
Albert Vaca
c7640967fe Merge branch 'master' into sslrefactor 2016-01-15 08:20:11 -08:00
Albert Vaca
15c2126387 Merge branch '0.9' 2016-01-15 08:19:19 -08:00
Albert Vaca
f0bcf84f6c Untranslated string 2016-01-15 08:15:55 -08:00
Holger Kaelberer
49d499d9a9 notifications: try to decode payload into largeIcon
Newer API levels scale large icons in notifications automatically,
olders don't. Therefore we downscale manually if necessary to avoid
too big icons.

REVIEW: 126667
2016-01-11 20:26:57 +01:00
Albert Vaca
a0f102b26a Commented SftpPlugin as we know it won't work trivially after the refactor. 2016-01-10 08:48:20 -08:00
Albert Vaca
d07ff5a802 SSHD gets confused if spongycastle is present. Changed to BouncyCastle.
It sees it as bouncycastle, tries to load some clases by they classpath and
fails.
2016-01-10 08:46:52 -08:00
Albert Vaca
2421abea22 Bumped a bunch of library and sdk versions 2016-01-10 08:22:56 -08:00
Albert Vaca
1ca09d7f58 Fixing build 2016-01-10 03:16:14 -08:00
Albert Vaca
3e9509123e Merge branch 'master' into sslmaster 2016-01-10 01:24:26 -08:00
Holger Kaelberer
8e990765da Fix loosing already known plugins on reloading
REVIEW: 126273
2016-01-05 20:35:52 +01:00
l10n daemon script
2b89c0ed15 SVN_SILENT made messages (after extraction) 2015-12-30 06:51:27 +00:00
l10n daemon script
3861e393e0 SVN_SILENT made messages (after extraction) 2015-12-21 07:50:08 +00:00
l10n daemon script
1c65f55d00 SVN_SILENT made messages (after extraction) 2015-12-09 07:02:43 +00:00
Holger Kaelberer
742332df1e notifications: accept remote notifications
Notification synchronization in working in both directions now
for API level > 17.
For older versions only receiving of notifications is enabled.

REVIEW: 125179
2015-12-07 21:44:30 +01:00
l10n daemon script
d992e6c4eb SVN_SILENT made messages (after extraction) 2015-12-06 07:04:04 +00:00
l10n daemon script
4312648e29 SVN_SILENT made messages (after extraction) 2015-12-05 07:05:07 +00:00
l10n daemon script
513f46aee1 SVN_SILENT made messages (after extraction) 2015-12-03 07:32:02 +00:00
Albert Vaca
f8da00b9b5 Merge branch 'master' into sslmaster 2015-12-02 09:46:22 -08:00
Rahil Momin
b2804a4378 Add natural scroll option to mousepad plugin
REVIEW: 126141
2015-12-01 03:40:05 -08:00
l10n daemon script
115420d2ce SVN_SILENT made messages (after extraction) 2015-11-30 07:59:29 +00:00
l10n daemon script
7cf2c7d916 SVN_SILENT made messages (after extraction) 2015-11-26 13:49:46 +00:00
l10n daemon script
fe37a29c53 SVN_SILENT made messages (after extraction) 2015-11-17 09:53:36 +00:00
Albert Vaca
78929ad31e Fixed a null pointer exception 2015-11-16 16:35:48 +01:00
Albert Vaca
f60920d556 Merge branch 'master' of git://anongit.kde.org/kdeconnect-android 2015-11-16 15:26:31 +01:00
Albert Vaca
1a16335f05 Merge branch '0.9' 2015-11-13 09:22:41 -08:00
Albert Vaca
1e8aedf582 Merge branch '0.9' 2015-11-12 08:23:26 -08:00
Albert Vaca
d67985d484 Now with less compilation errors! 2015-11-12 06:41:14 -08:00
Albert Vaca
857d754f8c Merge branch '0.9' 2015-11-12 06:34:14 -08:00
Albert Vaca
b7f21e05a8 Merge branch '0.9' 2015-11-11 17:10:42 -08:00
Albert Vaca
3ee9ee27cd Fixed broken link 2015-11-11 17:10:38 -08:00
l10n daemon script
fb8e4a7cc2 SVN_SILENT made messages (after extraction) 2015-11-11 07:49:25 +00:00
l10n daemon script
6258497989 SVN_SILENT made messages (after extraction) 2015-11-07 15:37:41 +00:00
l10n daemon script
9612dabb0a SVN_SILENT made messages (after extraction) 2015-11-01 08:25:22 +00:00
Albert Vaca
1e152581e6 Fixes a typo 2015-10-27 11:45:21 -07:00
Joao Carreira
d59750441f Changes in Contacts Helper
Modify the contact helper in oder to split contact name and
contact number. Those values are sent separately
2015-10-27 11:44:04 -07:00
Malte S. Stretz
61ded9469c Always unlock all mutexes
In a few places ReentrantLocks are used as mutexes.  In case of an
exception happening in the critical section the locks are never
released though.  A ReentrantLock should (almost) always be followed
by a try-finally block as documented in
  http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html
2015-10-27 11:44:04 -07:00
l10n daemon script
f82c01eb66 SVN_SILENT made messages (after extraction) 2015-10-27 09:49:21 +00:00
l10n daemon script
54881a65fa SVN_SILENT made messages (after extraction) 2015-10-26 01:45:55 +00:00
l10n daemon script
1630ca9a25 SVN_SILENT made messages (after extraction) 2015-10-13 10:22:15 +00:00
l10n daemon script
39db14b2ab SVN_SILENT made messages (after extraction) 2015-10-06 10:15:32 +00:00
l10n daemon script
1c13fe90a5 SVN_SILENT made messages (after extraction) 2015-10-03 08:53:40 +00:00
l10n daemon script
d283ed6ecc SVN_SILENT made messages (after extraction) 2015-10-02 08:09:17 +00:00
l10n daemon script
0ff6893b39 SVN_SILENT made messages (after extraction) 2015-09-30 07:59:12 +00:00
l10n daemon script
977fc8a7b6 SVN_SILENT made messages (after extraction) 2015-09-29 09:50:59 +00:00
l10n daemon script
7abace799a SVN_SILENT made messages (after extraction) 2015-09-28 08:20:49 +00:00
l10n daemon script
3207066928 SVN_SILENT made messages (after extraction) 2015-09-22 10:12:26 +00:00
l10n daemon script
12b84811ab SVN_SILENT made messages (after extraction) 2015-09-21 08:07:28 +00:00
l10n daemon script
b66fe45b4f SVN_SILENT made messages (after extraction) 2015-09-19 09:09:32 +00:00
Albert Vaca
9322970d58 Merge branch '0.9'
# Conflicts:
#	res/values-ca/strings.xml
#	res/values-fi/strings.xml
#	res/values-nl/strings.xml
#	res/values-pt-rBR/strings.xml
#	res/values-pt/strings.xml
#	res/values-sv/strings.xml
#	res/values-uk/strings.xml
#	res/values/strings.xml
#	src/org/kde/kdeconnect/Device.java
2015-09-18 08:11:44 -07:00
l10n daemon script
a9676be722 SVN_SILENT made messages (after extraction) 2015-09-18 07:59:16 +00:00
l10n daemon script
f3f80893d2 SVN_SILENT made messages (after extraction) 2015-09-17 09:26:40 +00:00
Albert Vaca
6035b91848 Merge branch 'master' of github.com:albertvaka/kdeconnect-android
# Conflicts:
#	store/header.png
#	store/header.xcf
2015-09-16 09:38:59 -07:00
Albert Vaca
3648ee32b4 Fixed header size 2015-09-16 09:36:49 -07:00
Albert Vaca
e464c993c2 Merge pull request #3 from micahdenn/master
Modified icon for better display on light backgrounds.
2015-09-16 17:41:37 +02:00
Micah Denn
b342199ed6 Modified icon for better display on light backgrounds and better compliance with material design. 2015-09-16 16:24:52 +01:00
Albert Vaca
6ef91dbb54 Merge branch 'master' of github.com:albertvaka/kdeconnect-android 2015-09-16 07:52:53 -07:00
Albert Vaca
278a358b71 Merge pull request #2 from LukeStonehm/master
Use 'ActionBarDrawerToggle'
2015-09-16 16:14:46 +02:00
l10n daemon script
a3f331f600 SVN_SILENT made messages (after extraction) 2015-09-16 08:15:54 +00:00
Luke Johnstone
e985e3648c UPDATED: • Now implementing 'ActionBarDrawerToggle' -> uses built in drawer icon (with nice built in animation) OCD fix.
• added "open"& "close" string -> inconsequential strings, user will never see these strings, but function requires them.
            Translate strings when you can :)
2015-09-16 09:52:55 +02:00
l10n daemon script
dff28d7ee7 SVN_SILENT made messages (after extraction) 2015-09-14 08:11:38 +00:00
l10n daemon script
13dd8a36bd SVN_SILENT made messages (after extraction) 2015-09-13 08:29:41 +00:00
Albert Vaca
2dbb1fcb11 Added new store header 2015-09-12 15:32:30 -07:00
Albert Vaca
13369b078b Added new assets for the store 2015-09-12 14:34:31 -07:00
Albert Vaca
80026f6d62 Merge branch 'stable' 2015-09-12 13:30:21 -07:00
Albert Vaca
0732f94a78 Fixed outgoing interfaces 2015-09-12 08:51:00 -07:00
Albert Vaca
33c233c780 Fixed capabilities being loaded for failed plugins 2015-09-12 08:06:12 -07:00
Albert Vaca
9c29cbf594 Load plugins in the same thread, so we know if they failed or not
This way we can update the capabilities accordingly.
2015-09-12 07:29:45 -07:00
Albert Vaca
6940bac3e6 Do not assume that all the plugins are non-null 2015-09-12 07:17:14 -07:00
Aleix Pol
629b38dec9 Implement the RunCommand plugin
Reviewed by Albert Vaca
2015-09-12 12:28:27 +02:00
Aleix Pol
effa740db4 Don't block plugins if they don't have any capabilities
Reviewed by Albert Vaca
2015-09-12 12:25:22 +02:00
Aleix Pol
3de229f1e5 Fix crash during exception catch
Reviewed by Albert Vaca
2015-09-12 12:24:38 +02:00
Aleix Pol
9b03bdf2c0 Make it possible to trigger a plugin page reload
Reviewed by Albert Vaca
2015-09-12 12:24:11 +02:00
Aleix Pol
56d6cffb71 Extend EntryItem
Makes it possible to use a subtitle in it, if provided.

Reviewed by Albert Vaca
2015-09-12 12:23:30 +02:00
l10n daemon script
a54c0ac4bd SVN_SILENT made messages (after extraction) 2015-09-12 09:17:32 +00:00
Albert Vaca
83415c699c Added an icon for runcommand and updated mpris icon 2015-09-12 02:15:41 -07:00
Albert Vaca
a6b590e477 Updated share_plugin_action icons 2015-09-12 02:11:40 -07:00
David Edmundson
8f53142a16 Add missing file 2015-09-11 21:45:39 +02:00
David Edmundson
8aebaaea2f Implement a "Find my phone" plugin
This makes the phone sound an alarm sound until dismissed. Even when
silent.

Reviewed-by: Albert Vaca
2015-09-11 21:11:01 +02:00
Albert Vaca
f5725b7c8d Merge branch 'master' into ssl
# Conflicts:
#	build.gradle
#	src/org/kde/kdeconnect/Backends/BaseLink.java
#	src/org/kde/kdeconnect/Backends/BaseLinkProvider.java
#	src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java
#	src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java
#	src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java
#	src/org/kde/kdeconnect/BackgroundService.java
#	src/org/kde/kdeconnect/Device.java
#	src/org/kde/kdeconnect/Helpers/DeviceHelper.java
#	src/org/kde/kdeconnect/UserInterface/DeviceActivity.java
#	src/org/kde/kdeconnect/UserInterface/PairActivity.java
#	tests/org/kde/kdeconnect/LanLinkProviderTest.java
#	tests/org/kde/kdeconnect/LanLinkTest.java
2015-09-11 09:24:35 -07:00
Albert Vaca
291abddeba Merge branch 'stable' 2015-09-11 06:07:41 -07:00
Albert Vaca
2c829af4e5 Renamed capabilities so we don't clash with KDE4's broken implementation 2015-09-11 05:20:19 -07:00
Albert Vaca
4456210953 Merge branch 'stable' 2015-09-11 04:45:06 -07:00
Albert Vaca
8307850805 Merge branch 'stable' 2015-09-10 15:52:21 -07:00
Albert Vaca
424763e3b6 Moved the unique ID out of networkpackage se we don't need to create one 2015-09-10 09:01:12 -07:00
Albert Vaca
7fc7a37be4 Merge branch 'stable' 2015-09-10 08:12:22 -07:00
Albert Vaca
132a223b8c Merge branch 'stable'
# Conflicts:
#	src/org/kde/kdeconnect/Device.java
2015-09-10 07:52:37 -07:00
Albert Vaca
35dd42f560 Fixed some potential concurrent modification exceptions
# Conflicts:
#	src/org/kde/kdeconnect/Device.java
2015-09-10 07:49:28 -07:00
l10n daemon script
5777dfe238 SVN_SILENT made messages (after extraction) 2015-09-10 03:18:40 +00:00
Albert Vaca
d7123e3435 Merge branch 'stable' 2015-09-09 14:58:01 -07:00
Albert Vaca
37f6c48d56 Merge branch 'stable' 2015-09-09 01:51:35 -07:00
Albert Vaca
1fe01db26e Assume computer by default from the phone for device with no type 2015-09-09 01:51:32 -07:00
Albert Vaca
f56fc9ceb8 Device type should be "desktop", no "computer" 2015-09-09 01:51:03 -07:00
Albert Vaca
1caa44b0ab Make it look better on old Androids 2015-09-09 01:51:03 -07:00
Albert Vaca
13ec8d7a3f Fixed crash on older Androids 2015-09-09 01:51:03 -07:00
l10n daemon script
0eda30b135 SVN_SILENT made messages (after extraction) 2015-09-09 01:59:41 +00:00
Aleix Pol
4fa0a89691 Merge branch 'stable' 2015-09-09 00:46:53 +02:00
Albert Vaca
476f7a7840 Implemented capabilities 2015-09-08 15:05:32 -07:00
Albert Vaca
27723c697b Added capabilities to plugins 2015-09-08 14:54:04 -07:00
Albert Vaca
6d28726c3b Show connected devices in pairingfragment because otherwise it's confusing 2015-09-08 14:45:01 -07:00
Albert Vaca
4c0bafa60f Fixed crash 2015-09-08 14:39:07 -07:00
Albert Vaca
ab131863fd Commented out some logs 2015-09-08 05:18:57 -07:00
Albert Vaca
506bc8d2c2 Started implementing the Send SMS plugin 2015-09-08 01:27:07 -07:00
Albert Vaca
b4f8d037e6 Prettier message 2015-09-08 01:27:07 -07:00
Albert Vaca
0b50bd8747 Don't show buttons that can't be clicked 2015-09-08 01:27:07 -07:00
Albert Vaca
da0d5af9d1 Ooops 2015-09-08 01:27:07 -07:00
Vineet Garg
b87aead06b Using reverse connection strategy for device with android version less than ICS
Enabled cipher suites manually due to issues caused by DHE by older devices
2015-08-25 01:06:49 +05:30
Vineet Garg
f908f5d8a1 Added spongy castle back, to support certificate generation on older devices which is not possible using bouncy castle due to same class name conflict
Fixed TLS version to TLSv1, this saves a connection failure due to TLSv1.2 hello message from devices with latest android versions

If device with new android version enquires about certificate from device with old version (<14) using setNeedClientAuth, connection fails on older device due to variation in certificate request code. So disabled server loop on older devices and using reverse connection hack for successful connection. But due to this, they can't connect to devices with similar android vesion since server is disabled on both and for a successful connection one should be server and one should be client
2015-08-24 02:03:55 +05:30
Vineet Garg
c4a27255a7 Removed spongycastle, now using bouncy castle version
Added reverse connection blacklist again
2015-08-23 00:11:36 +05:30
Vineet Garg
8cd59e701a Using custom built Netty 4.0.25, with fix for high memory usage.
This version does not have any error, as of now and working fine

Remove timer based blacklist for reverse connection since it is not needed now
2015-08-20 23:48:02 +05:30
Vineet Garg
8556e9399e Device unpairs only on handshake exception, not on any exception 2015-08-20 19:15:51 +05:30
Vineet Garg
6a4f0e79a9 Added timer based reverse connection blacklist to avoid infinite loop when two Android 5.0 devices are trying to connect
Changed netty all to netty handler, netty handler is smaller in size and contains required components
2015-08-20 17:05:13 +05:30
Vineet Garg
cf4b840509 OOPS! used device name instead of link name in lan pairing handler 2015-08-17 23:54:10 +05:30
Vineet Garg
50fcea2cd9 Removed conscrypt
Fixed issue in netty, currently use custom build netty
Removed multi dex
2015-08-17 22:56:18 +05:30
Vineet Garg
7c9d6630d0 Show encryption info in device activity too 2015-08-15 18:44:49 +05:30
Vineet Garg
7fec58d6b2 We generate certificate for years present date - 1 to present date + 9, this avoid the case of certificate not valid due to different time zones on devices 2015-08-14 17:49:35 +05:30
Vineet Garg
cc7a1f5a2b Added default value while checking for link name in pairing handler, this provides backward compatibility 2015-08-14 17:34:35 +05:30
Vineet Garg
5b0876f424 Merge branch 'master' into netty 2015-08-13 15:03:36 +05:30
Vineet Garg
ce5d2c8394 Reimplemented pairing handler interface, now it it is similar to kde version 2015-08-13 14:55:00 +05:30
Albert Vaca
f715c0797b Merge branch 'master' of git://anongit.kde.org/kdeconnect-android 2015-08-02 12:57:28 -07:00
Vineet Garg
6314e4217e Merge branch 'master' into netty 2015-07-19 22:11:20 +05:30
Vineet Garg
f7f00057c9 Fixed certificate fingerprint formatting 2015-07-19 19:10:09 +05:30
Vineet Garg
b084c8653e Show full hash
Certifcates shown are wrongly ordered
2015-07-19 04:34:14 +05:30
Vineet Garg
d1dc0ba2b2 Commit from pc 2015-07-18 18:57:09 +05:30
Vineet Garg
fd5063e7a7 Enabled multi dex 2015-07-01 03:29:40 +05:30
Vineet Garg
442c01a8e5 Added x86 and mips conscrypt libs
Pairing is now done through links
2015-07-01 03:25:16 +05:30
Vineet Garg
72ad2e010c Try when adding security provider 2015-06-28 21:30:06 +05:30
Vineet Garg
67227f4aa0 Added conscrypt readme 2015-06-28 20:40:45 +05:30
Vineet Garg
d93eb5d71e Added conscrypt, open ssl based provider for android
Fixed some isssues in CR
2015-06-28 20:07:05 +05:30
Vineet Garg
aeb9a717d4 Removed some log statements, fixed issues 2015-06-25 19:54:37 +05:30
Vineet Garg
1b726018d3 Fixed bugs pointed out by Albert on CR 2015-06-25 04:20:03 +05:30
Vineet Garg
ed6aef42a6 Removed mina core from build.gradle 2015-06-21 17:50:56 +05:30
Vineet Garg
02826ccfe4 Modified build tools version 2015-06-21 17:47:40 +05:30
Vineet Garg
00557052ff Corrected unit tests
Settings were not save properly when certificate was null
Removed LanLinkProvider test, impossible with new design
2015-06-21 17:30:29 +05:30
Vineet Garg
7be045e7c4 Added global preference to use ssl or not in case ssl is not working on some devices 2015-06-20 22:57:11 +05:30
Vineet Garg
8128c42824 Added support to send payload over ssl 2015-06-20 20:54:39 +05:30
Vineet Garg
41100ad371 Merge branch 'master' into netty 2015-06-20 17:50:00 +05:30
Vineet Garg
00a4d39bf6 Added support to verify keys during pair 2015-06-20 17:48:58 +05:30
Vineet Garg
8084b92990 No ecnryption when link is on ssl 2015-06-20 04:21:30 +05:30
Vineet Garg
e14f5a0df1 Ssl support added
Showing high cpu usage on some devices
2015-06-20 04:09:02 +05:30
Vineet Garg
af548ae949 Removed Udp Channel from LanLinkProvider, had no use 2015-06-19 15:49:07 +05:30
Vineet Garg
2f16656aa0 Initial netty implementation 2015-06-19 04:00:27 +05:30
Albert Vaca
b6b4850355 De-duplicated code 2015-02-01 00:08:49 -08:00
118 changed files with 3813 additions and 1043 deletions

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.kde.kdeconnect_tp"
android:versionCode="909"
android:versionName="0.9e">
android:versionCode="1000"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="22" />
@@ -14,21 +14,16 @@
android:smallScreens="true"
android:xlargeScreens="true" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
android:required="false" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
<uses-permission
android:name="android.permission.RECEIVE_SMS"
android:required="false" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false" />
<!-- <uses-permission android:name="android.permission.SEND_SMS" android:required="false" /> -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -119,6 +114,12 @@
-->
</receiver>
<activity
android:name="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity"
android:label="@string/findmyphone_title"
android:launchMode="singleInstance">
</activity>
<!-- Plugin-related activities and services -->
<activity
@@ -130,6 +131,15 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MaterialActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandActivity"
android:label="@string/remote_control"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MaterialActivity"
>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MaterialActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
android:configChanges="orientation|keyboardHidden|screenSize"

View File

@@ -22,4 +22,4 @@ You can install this app from the [Play Store](https://play.google.com/store/app
## License
[GNU GPL v2](https://www.gnu.org/licenses/gpl-2.0.html) and [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html)
If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://projects.kde.org/projects/playground/base/kdeconnect-android/repository/).
If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://projects.kde.org/projects/extragear/network/kdeconnect-android/repository/).

View File

@@ -7,7 +7,7 @@ function export_pot_file # First parameter will be the path of the pot file we h
{
potfile=$1
mkdir outdir
a2po export --android res/ --gettext outdir
ANSI_COLORS_DISABLED=1 a2po export --android res/ --gettext outdir
mv outdir/template.pot $potfile
rm -rf outdir
}
@@ -15,7 +15,9 @@ function export_pot_file # First parameter will be the path of the pot file we h
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 --ignore-fuzzy --android res/ --gettext $podir
ANSI_COLORS_DISABLED=1 a2po import --ignore-fuzzy --android res/ --gettext $podir
#Android doesn't support languages with an @
rm -r res/values-*@*
}

View File

@@ -3,18 +3,28 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.android.tools.build:gradle:1.5.0'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion '22.0.1'
buildToolsVersion '23.0.2'
compileSdkVersion 23
defaultConfig {
minSdkVersion 9
targetSdkVersion 22
targetSdkVersion 22 //Bumping to 23 means we have to support the new permissions model
multiDexEnabled true
}
dexOptions {
javaMaxHeapSize "4g"
}
compileOptions {
// Use Java 1.7, requires minSdk 8
//SSHD requires mina when running on JDK < 7
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {
main {
@@ -32,13 +42,17 @@ android {
pickFirst "META-INF/DEPENDENCIES"
pickFirst "META-INF/LICENSE"
pickFirst "META-INF/NOTICE"
pickFirst "META-INF/BCKEY.SF"
pickFirst "META-INF/BCKEY.DSA"
pickFirst "META-INF/INDEX.LIST"
pickFirst "META-INF/io.netty.versions.properties"
}
lintOptions {
abortOnError false
checkReleaseBuilds false
}
buildTypes {
release {
release { //keep on 'releae', set to 'all' when testing to make sure proguard is not deleting important stuff
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
@@ -49,19 +63,20 @@ dependencies {
repositories {
mavenCentral()
}
compile 'com.android.support:support-v4:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
compile 'com.android.support:support-v4:23.2.1'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'org.apache.mina:mina-core:2.0.9'
compile 'org.apache.sshd:sshd-core:0.8.0'
compile 'org.bouncycastle:bcprov-jdk16:1.46'
compile 'org.apache.sshd:sshd-core:0.8.0' //0.9 seems to fail on Android 6 and 1.+ requires java.nio.file, which doesn't exist in Android
//compile 'org.bouncycastle:bcprov-jdk15on:1.54'
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
compile 'io.netty:netty-handler:4.1.0.CR3'
androidTestCompile 'org.mockito:mockito-core:1.10.19'
// Because mockito has some problems with dex environment
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'
//compile fileTree(dir: 'libs', include: '*.jar')
//compile fileTree(include: '*.jar', dir: 'libs')
}

14
proguard-rules.pro vendored
View File

@@ -16,15 +16,23 @@
# public *;
#}
-dontobfuscate
# Allow obfuscation of android.support.v7.internal.view.menu.**
# to avoid problem on Samsung 4.2.2 devices with appcompat v21
# see https://code.google.com/p/android/issues/detail?id=78377
-keep class !android.support.v7.internal.view.menu.**,** {*;}
-keepnames class !android.support.v7.internal.view.menu.**,android.support.v7.** {*;}
-dontwarn org.spongycastle.**
-dontwarn org.bouncycastle.**
-dontwarn org.apache.sshd.**
-dontwarn org.apache.mina.**
-dontwarn org.bouncycastle.**
-dontwarn org.slf4j.**
-dontwarn io.netty.**
-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,LineNumberTable,Signature,*Annotation*
-keep class org.spongycastle.** {*;}
-keep class org.bouncycastle.** {*;}
-keep class org.kde.kdeconnect.** {*;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -74,10 +74,14 @@
android:id="@+id/unpair_message"
android:visibility="gone"
android:layout_width="match_parent"
android:drawableStart="@drawable/ic_error_outline_black_48dp"
android:drawableLeft="@drawable/ic_error_outline_black_48dp"
android:drawablePadding="8dip"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/unreachable_description"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center" />
/>
<ListView
android:id="@+id/buttons_list"

View File

@@ -0,0 +1,26 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity">
<Button
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/findmyphone_found"
android:textSize="50dp"
android:id="@+id/bFindMyPhone"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>

View File

@@ -12,22 +12,27 @@
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:elevation="8dp"
android:background="?attr/colorPrimary" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="@+id/navigation_drawer"
android:background="@drawable/state_list_drawer_background"
app:itemBackground="@drawable/state_list_drawer_background"
app:itemTextColor="@color/state_list_drawer_text"
app:itemIconTint="@color/state_list_drawer_text"

View File

@@ -29,8 +29,8 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:textColor="#CC2222"
android:visibility="gone"
android:textColor="@android:color/darker_gray"
android:text="" />

View File

@@ -7,9 +7,9 @@
android:layout_height="match_parent"
android:id="@+id/mpris_control_view"
android:gravity="center"
android:paddingLeft="60dip"
android:paddingLeft="30dip"
android:paddingTop="5dip"
android:paddingRight="60dip"
android:paddingRight="30dip"
android:paddingBottom="5dip">
<TextView
@@ -32,6 +32,9 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/now_playing_textview"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:gravity="center"
android:padding="8dip"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="@drawable/drawer_header"
@@ -55,4 +55,4 @@
/>
-->
</RelativeLayout>
</LinearLayout>

View File

@@ -27,6 +27,13 @@
<item/>
<item>Nothing</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">الأجهزة المقترن بها</string>
<string name="category_not_paired_devices">الأجهزة المتوفّرة</string>
<string name="category_remembered_devices">الأجهزة المتذكَّرة</string>

159
res/values-ast/strings.xml Normal file
View File

@@ -0,0 +1,159 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Notificador telefónicu</string>
<string name="pref_plugin_telephony_desc">Unvia avisos pa SMS y llamaes</string>
<string name="pref_plugin_battery">Informe de batería</string>
<string name="pref_plugin_battery_desc">Infoma davezu del estáu de la batería</string>
<string name="pref_plugin_sftp">Esposición del sistema de ficheros</string>
<string name="pref_plugin_sftp_desc">Permite restolar remotamente\'l sistema de ficheros del teléfonu</string>
<string name="pref_plugin_clipboard">Sincronización del cartafueyu</string>
<string name="pref_plugin_clipboard_desc">Comparte\'l conteníu del cartafueyu</string>
<string name="pref_plugin_mousepad">Entrada remota</string>
<string name="pref_plugin_mousepad_desc">Usa\'l to teléfonu como mur y tecláu</string>
<string name="pref_plugin_mpris">Controles remotos multimedia</string>
<string name="pref_plugin_mpris_desc">Controla l\'audiu/videu dende\'l to teléfonu</string>
<string name="pref_plugin_runcommand">Executar comandu</string>
<string name="pref_plugin_runcommand_desc">Executa un comandu nel to sistema</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Unvia y recibe pings</string>
<string name="pref_plugin_notifications">Sincronización d\'avisos</string>
<string name="pref_plugin_notifications_desc">Accede a los tos avisos dende otros preseos</string>
<string name="pref_plugin_sharereceiver">Compartir y recibir</string>
<string name="pref_plugin_sharereceiver_desc">Comparte ficheros y URLs ente preseos</string>
<string name="plugin_not_available">Esta carauterística nun ta disponible na to versión d\'Android</string>
<string name="device_list_empty">Ensin preseos</string>
<string name="ok">Aceutar</string>
<string name="cancel">Encaboxar</string>
<string name="open_settings">Abrir axustes</string>
<string name="no_permissions">Necesites garantizar l\'accesu a los avisos</string>
<string name="send_ping">Unviar ping</string>
<string name="open_mpris_controls">Controles multimedia</string>
<string name="open_mousepad">Entrada remota</string>
<string name="mousepad_info">Muevi un deu enriba la pantalla pa mover el mur. Calca pa un clic y usa dos/tres deos pa los botones de drecha y en mediu. Usa un primíu llargu p\'arrastrar y soltar.</string>
<string name="mousepad_double_tap_settings_title">Afitar aición de calcu con dos deos</string>
<string name="mousepad_triple_tap_settings_title">Afitar aición de calcu con tres deos</string>
<string name="mousepad_sensitivity_settings_title">Afitar sensibilidá del panel táutil</string>
<string name="mousepad_scroll_direction_title">Direición inversa de desplazamientu</string>
<string-array name="mousepad_tap_entries">
<item>Clic drechu</item>
<item>Clic d\'en mediu</item>
<item>Nada</item>
</string-array>
<string name="mousepad_double_default">drecha</string>
<string name="mousepad_triple_default">d\'en mediu</string>
<string name="mousepad_sensitivity_default">por defeutu</string>
<string-array name="mousepad_sensitivity_entries">
<item>La más lenta</item>
<item>Above Slowest</item>
<item>Predeterminada</item>
<item>Penriba lo predeterminao</item>
<item>La más rápida</item>
</string-array>
<string name="category_connected_devices">Preseos coneutaos</string>
<string name="category_not_paired_devices">Preseos disponibles</string>
<string name="category_remembered_devices">Preseos recordaos</string>
<string name="plugins_failed_to_load">Los complementos fallaron al cargase (calca pa más información):</string>
<string name="device_menu_plugins">Axustes de complementos</string>
<string name="device_menu_unpair">Desempareyar</string>
<string name="device_not_reachable">El preséu empareyáu nun ye agamable</string>
<string name="pair_new_device">Empareyar preséu nuevu</string>
<string name="unknown_device">Preséu desconocíu</string>
<string name="error_not_reachable">Nun ye algamable\'l preséu</string>
<string name="error_already_requested">Empareyamientu yá solicitáu</string>
<string name="error_already_paired">El preséu yá ta empareyáu</string>
<string name="error_could_not_send_package">Nun pudo unviase\'l paquete</string>
<string name="error_timed_out">Tiempu escosao</string>
<string name="error_canceled_by_user">Encaboxáu pol usuariu</string>
<string name="error_canceled_by_other_peer">Encaboxáu pola otra parte</string>
<string name="error_invalid_key">Recibióse una clave non válida</string>
<string name="pair_requested">Solicitóse l\'empareyamientu</string>
<string name="pairing_request_from">Solicitú d\'empareyamientu de %1s</string>
<string name="received_url_title">Recibióse l\'enllaz de %1s</string>
<string name="received_url_text">Calca p\'abrir «%1s»</string>
<string name="incoming_file_title">Ficheru entrante de %1s</string>
<string name="incoming_file_text">%1s</string>
<string name="outgoing_file_title">Unviando ficheru a %1s</string>
<string name="outgoing_file_text">%1s</string>
<string name="received_file_title">Recibióse\'l ficheru de %1s</string>
<string name="received_file_fail_title">Fallu al recibir el ficheru de %1s</string>
<string name="received_file_text">Tap to open \'%1s\'</string>
<string name="sent_file_title">Unvióse\'l ficheru a %1s</string>
<string name="sent_file_text">%1s</string>
<string name="sent_file_failed_title">Fallu al unviar el ficheru %1s</string>
<string name="sent_file_failed_text">%1s</string>
<string name="tap_to_answer">Calca pa responder</string>
<string name="reconnect">Reconeutar</string>
<string name="right_click">Unviar clic drechu</string>
<string name="middle_click">Unviar clic d\'en mediu</string>
<string name="show_keyboard">Amosar tecláu</string>
<string name="device_not_paired">Preséu non empareyáu</string>
<string name="request_pairing">Solicitar empareyamientu</string>
<string name="pairing_accept">Aceutar</string>
<string name="pairing_reject">Refugar</string>
<string name="device">Preséu</string>
<string name="pair_device">Empareyar preséu</string>
<string name="remote_control">Control remotu</string>
<string name="settings">Axustes 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ápidu</string>
<string name="mpris_next">Siguiente</string>
<string name="mpris_volume">Volume</string>
<string name="mpris_settings">Axustes multimedia</string>
<string name="mpris_time_settings_title">Botones d\'avanzar/rebobinar</string>
<string name="mpris_time_settings_summary">Axusta\'l tiempu p\'avanzar/rebobinar al primise</string>
<string-array name="mpris_time_entries">
<item>10 segundos</item>
<item>20 segundos</item>
<item>30 segundos</item>
<item>1 minutu</item>
<item>2 minutos</item>
</string-array>
<string name="share_to">Compartir en...</string>
<string name="protocol_version_older">Esti preséu usa una versión vieya del protocolu</string>
<string name="protocol_version_newer">Esti preséu usa una versión anovada del protocolu</string>
<string name="general_settings">Axustes xenerales</string>
<string name="plugin_settings">Axustes</string>
<string name="plugin_settings_with_name">Axustes de %s</string>
<string name="device_name">Nome de preséu</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Nome de preséu non válidu</string>
<string name="shareplugin_text_saved">Recibióse\'l testu y guardóse nel cartafueyu</string>
<string name="custom_devices_settings">Llista de preseos personalizada</string>
<string name="pair_device_action">Empareyar con un preséu nuevu</string>
<string name="unpair_device_action">Desempareyar %s</string>
<string name="custom_device_list">Amestar preseos pola IP</string>
<string name="share_notification_preference">Avisos sonoros</string>
<string name="share_notification_preference_summary">Fai vibrar y reproduz un soníu al recibir un ficheru</string>
<string name="title_activity_notification_filter">Peñera d\'avisos</string>
<string name="filter_apps_info">Los avisos sincronizaránse coles aplicaciones esbillaes.</string>
<string name="sftp_internal_storage">Almacenamientu internu</string>
<string name="sftp_all_files">Tolos ficheros</string>
<string name="sftp_sdcard_num">Tarxeta SD %d</string>
<string name="sftp_sdcard">Tarxeta SD</string>
<string name="sftp_readonly">(namai llectura)</string>
<string name="sftp_camera">Semeyes de cámara</string>
<string name="add_host">Amestar agospiu/IP</string>
<string name="add_host_hint">Nome d\'agospiu o IP</string>
<string name="no_players_connected">Nun s\'alcontraron reproductores</string>
<string name="custom_dev_list_help">Usa esta opción namái si\'l to preséu nun se deteuta automáticamente. Introduz la direición o\'l nome d\'agospiu y toca\'l botón p\'amestalu a la llista. Toca un elementu esistente pa desanicialu de la llista.</string>
<string name="mpris_player_on_device">%1$s en %2$s</string>
<string name="send_files">Unviar ficheros</string>
<string name="pairing_title">Preseos KDE Connect</string>
<string name="pairing_description">Equí deberíen apaecer otros preseos executando KDE Connect.</string>
<string name="device_paired">Preséu empareyáu</string>
<string name="device_rename_title">Renomar preséu</string>
<string name="device_rename_confirm">Renomar</string>
<string name="refresh">Refrescar</string>
<string name="unreachable_description">Esti preséu empareyáu nun ye algamable. Asegúrate que ta coneutáu a la to mesma rede.</string>
<string name="no_file_browser">Nun hai restoladores de ficheros instalaos.</string>
<string name="pref_plugin_telepathy">Unviar SMS</string>
<string name="pref_plugin_telepathy_desc">Unvia mensaxes de testu dende\'l to escritoriu</string>
<string name="plugin_not_supported">Esti complementu nun ta sofitáu pol preséu</string>
<string name="findmyphone_title">Alcuéntra\'l mio teléfonu</string>
<string name="findmyphone_description">Fai sonar esti teléfonu pa que pueas alcontralu.</string>
<string name="findmyphone_found">Alcontrar</string>
<string name="open">Abrir</string>
<string name="close">Zarrar</string>
</resources>

View File

@@ -25,6 +25,13 @@
<item>Middle click</item>
<item>Nothing</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Свързани устройства</string>
<string name="category_remembered_devices">Запомнени устройства</string>
<string name="plugins_failed_to_load">Неуспешно зареждане на приставки (докоснете за подробности)</string>

View File

@@ -32,6 +32,13 @@
</string-array>
<string name="mousepad_double_default">desno</string>
<string name="mousepad_triple_default">Srednje</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Spojeni uređaji</string>
<string name="category_not_paired_devices">Dostupni uređaji</string>
<string name="category_remembered_devices">Zapamćeni uređaji</string>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Moveu un dit per la pantalla per a moure el cursor del ratolí. Toqueu per un clic, i empreu dos/tres dits pels botons dret i mig. Empreu un toc llarg per arrossegar i deixar anar.</string>
<string name="mousepad_double_tap_settings_title">Estableix l\'acció de tocar amb dos dits</string>
<string name="mousepad_triple_tap_settings_title">Estableix l\'acció de tocar amb tres dits</string>
<string name="mousepad_sensitivity_settings_title">Estableix la sensibilitat del ratolí tàctil</string>
<string name="mousepad_scroll_direction_title">Inverteix la direcció del desplaçament</string>
<string-array name="mousepad_tap_entries">
<item>Clic dret</item>
<item>Clic del mig</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">dret</string>
<string name="mousepad_triple_default">mig</string>
<string name="mousepad_sensitivity_default">Predeterminada</string>
<string-array name="mousepad_sensitivity_entries">
<item>La més lenta</item>
<item>Lenta</item>
<item>Predeterminada</item>
<item>Ràpida</item>
<item>La més ràpida</item>
</string-array>
<string name="category_connected_devices">Dispositius connectats</string>
<string name="category_not_paired_devices">Dispositius disponibles</string>
<string name="category_remembered_devices">Dispositius recordats</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Arranjament del connector</string>
<string name="device_menu_unpair">Desparella</string>
<string name="device_not_reachable">No s\'ha pogut accedir al dispositiu parell</string>
<string name="pair_new_device">Aparella amb un dispositiu nou</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>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Troba el meu telèfon</string>
<string name="findmyphone_description">Fa sonar aquest telèfon perquè el pugueu trobar.</string>
<string name="findmyphone_found">L\'he trobat</string>
<string name="open">Obre</string>
<string name="close">Tanca</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Pohybujte prstem po obrazovce pro pohybování kurzorem myši. Ťukněte pro kliknutí a použijte dva/tři prsty jako pravé a prostřední tlačítko. Pro přetažení dlouze podržte.</string>
<string name="mousepad_double_tap_settings_title">Nastavit činnost pro ťuknutí dvěma prsty</string>
<string name="mousepad_triple_tap_settings_title">Nastavit činnost pro ťuknutí třemi prsty</string>
<string name="mousepad_sensitivity_settings_title">Nastavit citlivost touchpadu</string>
<string name="mousepad_scroll_direction_title">Obrácený směr posunu</string>
<string-array name="mousepad_tap_entries">
<item>Kliknutí pravým tlačítkem myši</item>
<item>Kliknutí prostředním tlačítkem myši</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">pravé</string>
<string name="mousepad_triple_default">prostřední</string>
<string name="mousepad_sensitivity_default">výchozí</string>
<string-array name="mousepad_sensitivity_entries">
<item>Nejpomalejší</item>
<item>Méně pomalý</item>
<item>Výchozí</item>
<item>Rychlejší</item>
<item>Nejrychlejší</item>
</string-array>
<string name="category_connected_devices">Připojená zařízení</string>
<string name="category_not_paired_devices">Dostupná zařízení</string>
<string name="category_remembered_devices">Zapamatovaná zařízení</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Nastavení modulů</string>
<string name="device_menu_unpair">Zrušit párování</string>
<string name="device_not_reachable">Spárované zařízení je nedostupné</string>
<string name="pair_new_device">Spárovat nové zařízení</string>
<string name="unknown_device">Neznámé zařízení</string>
<string name="error_not_reachable">Zařízení je nedostupné</string>
<string name="error_already_requested">Párování již bylo vyžádáno</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Najít můj telefon</string>
<string name="findmyphone_description">Prozvoní tento telefon, takže jej můžete najít.</string>
<string name="findmyphone_found">Nalezeno</string>
<string name="open">Otevřít</string>
<string name="close">Zavřít</string>
</resources>

View File

@@ -39,6 +39,13 @@
</string-array>
<string name="mousepad_double_default">højre</string>
<string name="mousepad_triple_default">midter</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Forbundne enheder</string>
<string name="category_not_paired_devices">Tilgængelig enheder</string>
<string name="category_remembered_devices">Huskede enheder</string>
@@ -46,6 +53,7 @@
<string name="device_menu_plugins">Plugin-indstillinger</string>
<string name="device_menu_unpair">Fjern parring</string>
<string name="device_not_reachable">Den parrede enhed kan ikke tilgås</string>
<string name="pair_new_device">Par med ny enhed</string>
<string name="unknown_device">Ukendt enhed</string>
<string name="error_not_reachable">Enheden kan ikke nås</string>
<string name="error_already_requested">Allerede anmodet om parring</string>
@@ -143,4 +151,6 @@
<string name="findmyphone_title">Find min telefon</string>
<string name="findmyphone_description">Ringer til denne telefon, så du kan finde den.</string>
<string name="findmyphone_found">Fundet</string>
<string name="open">Åbn</string>
<string name="close">Luk</string>
</resources>

View File

@@ -29,6 +29,7 @@
<string name="send_ping">Ping senden</string>
<string name="open_mpris_controls">Multimedia-Bedienung</string>
<string name="open_mousepad">Ferneingabe</string>
<string name="mousepad_scroll_direction_title">Bildlaufrichtung umkehren</string>
<string-array name="mousepad_tap_entries">
<item>Rechtsklick</item>
<item>Mittelklick</item>
@@ -36,6 +37,14 @@
</string-array>
<string name="mousepad_double_default">Rechts</string>
<string name="mousepad_triple_default">Mitte</string>
<string name="mousepad_sensitivity_default">Standard</string>
<string-array name="mousepad_sensitivity_entries">
<item>Langsamste</item>
<item>Above Slowest</item>
<item>Standard</item>
<item>Above Default</item>
<item>Schnellste</item>
</string-array>
<string name="category_connected_devices">Verbundene Geräte</string>
<string name="category_not_paired_devices">Verfügbare Gerät</string>
<string name="category_remembered_devices">Gemerkte Geräte</string>
@@ -43,6 +52,7 @@
<string name="device_menu_plugins">Modul-Einstellungen</string>
<string name="device_menu_unpair">Verbindung trennen</string>
<string name="device_not_reachable">Das angegeben Gerät ist nicht erreichbar</string>
<string name="pair_new_device">Ein neues Gerät verbinden</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>
@@ -106,6 +116,7 @@
<string name="pair_device_action">Ein neues Gerät verbinden</string>
<string name="unpair_device_action">Verbindung %s trennen</string>
<string name="custom_device_list">Geräte nach IP hinzufügen</string>
<string name="share_notification_preference">Ausführliche Benachrichtigungen</string>
<string name="share_notification_preference_summary">Beim Empfang einer Datei vibrieren und einen Sound abspielen</string>
<string name="title_activity_notification_filter">Benachrichtigungs-Filter</string>
<string name="filter_apps_info">Benachrichtigungen werden zwischen den ausgewählten Anwendungen abgeglichen.</string>
@@ -121,11 +132,18 @@
<string name="mpris_player_on_device">%1$s auf %2$s</string>
<string name="send_files">Dateien senden</string>
<string name="pairing_title">KDE-Connect-Geräte</string>
<string name="pairing_description">Andere Geräte, auf denen KDE-Connect läuft im gleichen Netzwerk,sollte hier angezeigt werden.</string>
<string name="device_paired">Gerät verbunden</string>
<string name="device_rename_title">Geräte umbenennen</string>
<string name="device_rename_confirm">Umbenennen</string>
<string name="refresh">Aktualisieren</string>
<string name="no_file_browser">Es sind keine Dateiverwaltungsprogramme installiert.</string>
<string name="pref_plugin_telepathy">SMS senden</string>
<string name="pref_plugin_telepathy_desc">Text-Nachrichten von Ihrer Arbeitsfläche senden</string>
<string name="plugin_not_supported">Dieses Modul wird durch das Gerät nicht unterstützt</string>
<string name="findmyphone_title">Telefon suchen</string>
<string name="findmyphone_description">Ruft dieses Telefon an, um es zu suchen.</string>
<string name="findmyphone_found">Gefunden</string>
<string name="open">Öffnen</string>
<string name="close">Schließen</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use a long press to drag\'n drop.</string>
<string name="mousepad_double_tap_settings_title">Set two finger tap action</string>
<string name="mousepad_triple_tap_settings_title">Set three finger tap action</string>
<string name="mousepad_sensitivity_settings_title">Set touchpad sensitivity</string>
<string name="mousepad_scroll_direction_title">Reverse Scrolling Direction</string>
<string-array name="mousepad_tap_entries">
<item>Right click</item>
<item>Middle click</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">right</string>
<string name="mousepad_triple_default">middle</string>
<string name="mousepad_sensitivity_default">default</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Connected devices</string>
<string name="category_not_paired_devices">Available devices</string>
<string name="category_remembered_devices">Remembered devices</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Plugin settings</string>
<string name="device_menu_unpair">Unpair</string>
<string name="device_not_reachable">Paired device not reachable</string>
<string name="pair_new_device">Pair new device</string>
<string name="unknown_device">Unknown device</string>
<string name="error_not_reachable">Device not reachable</string>
<string name="error_already_requested">Pairing already requested</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Find My Phone</string>
<string name="findmyphone_description">Rings this phone so you can find it.</string>
<string name="findmyphone_found">Found</string>
<string name="open">Open</string>
<string name="close">Close</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Mueva un dedo sobre la pantalla para mover el cursor del ratón. Pulse para ejecutar un clic y use dos/tres dedos para emular los botones derecho y central. Use una pulsación larga para arrastrar y soltar.</string>
<string name="mousepad_double_tap_settings_title">Establecer la acción al pulsar con dos dedos</string>
<string name="mousepad_triple_tap_settings_title">Establecer la acción al pulsar con tres dedos</string>
<string name="mousepad_sensitivity_settings_title">Establecer sensibilidad del panel táctil</string>
<string name="mousepad_scroll_direction_title">Invertir dirección de desplazamiento</string>
<string-array name="mousepad_tap_entries">
<item>Clic derecho</item>
<item>Clic del botón central</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">derecho</string>
<string name="mousepad_triple_default">medio</string>
<string name="mousepad_sensitivity_default">por defecto</string>
<string-array name="mousepad_sensitivity_entries">
<item>Muy poco sensible</item>
<item>Poco sensible</item>
<item>Predeterminada</item>
<item>Sensible</item>
<item>Muy sensible</item>
</string-array>
<string name="category_connected_devices">Dispositivos conectados</string>
<string name="category_not_paired_devices">Dispositivos disponibles</string>
<string name="category_remembered_devices">Dispositivos recordados</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Preferencias del complemento</string>
<string name="device_menu_unpair">Desvincular</string>
<string name="device_not_reachable">No se encuentra el dispositivo aparejado</string>
<string name="pair_new_device">Vincular nuevo dispositivo</string>
<string name="unknown_device">Dispositivo desconocido</string>
<string name="error_not_reachable">No se encuentra el dispositivo</string>
<string name="error_already_requested">Ya ha solicitado vincularse</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Encontrar mi teléfono</string>
<string name="findmyphone_description">Hace sonar este teléfono para que pueda encontrarlo.</string>
<string name="findmyphone_found">Encontrado</string>
<string name="open">Abrir</string>
<string name="close">Cerrar</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Liikuta hiiren osoitinta liikuttamalla sormeasi näytöllä. Napsauta napauttamalla yhdellä sormella, käytä oikeaa painiketta kahdella sormella ja keskipainiketta kolmella. Vedä ja pudota painamalla pitkään.</string>
<string name="mousepad_double_tap_settings_title">Aseta kahden sormen napautuksen toiminto</string>
<string name="mousepad_triple_tap_settings_title">Aseta kolmen sormen napautuksen toiminto</string>
<string name="mousepad_sensitivity_settings_title">Aseta kosketuslevyn herkkyys</string>
<string name="mousepad_scroll_direction_title">Käänteinen vierityssuunta</string>
<string-array name="mousepad_tap_entries">
<item>Oikea napsautus</item>
<item>Keskinapsautus</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">Oikea painike</string>
<string name="mousepad_triple_default">Keskipainike</string>
<string name="mousepad_sensitivity_default">oletus</string>
<string-array name="mousepad_sensitivity_entries">
<item>Hitain</item>
<item>Hitainta suurempi</item>
<item>Oletus</item>
<item>Oletusta suurempi</item>
<item>Nopein</item>
</string-array>
<string name="category_connected_devices">Yhdistetyt laitteet</string>
<string name="category_not_paired_devices">Saatavilla olevat laitteet</string>
<string name="category_remembered_devices">Muistetut laitteet</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Liitännäisten asetukset</string>
<string name="device_menu_unpair">Poista laitepari</string>
<string name="device_not_reachable">Laitepari tavoittamattomissa</string>
<string name="pair_new_device">Kytke uusi laite pariksi</string>
<string name="unknown_device">Tuntematon laite</string>
<string name="error_not_reachable">Laite tavoittamattomissa</string>
<string name="error_already_requested">Pariksi kytkemistä on jo pyydetty</string>
@@ -126,13 +137,13 @@
<string name="add_host">Lisää kone/IP</string>
<string name="add_host_hint">Konenimi tai IP-osoite</string>
<string name="no_players_connected">Soittimia ei löytynyt</string>
<string name="custom_dev_list_help">Käytä tätä vain, jos laitettasi ei tunnisteta automaattisesti. Kirjoita IP-osoite tai konenimi alle ja kosketa painiketta lisätäksesi sen luetteloon. Kosketa olemassa olevaa kohtaa poistaaksesi sen luettelosta.</string>
<string name="custom_dev_list_help">Käytä tätä vain, jos laitettasi ei tunnisteta automaattisesti. Kirjoita IP-osoite tai konenimi alle ja lisää se luetteloon koskettamalla painiketta. Voit poistaa olemassa olevan kohdan luettelosta koskettamalla sitä.</string>
<string name="mpris_player_on_device">%1$s laitteella %2$s</string>
<string name="send_files">Lähetä tiedostoja</string>
<string name="pairing_title">KDE Connect -laitteet</string>
<string name="pairing_description">Muiden samassa verkossa olevien KDE Connectia käyttävien laitteiden pitäisi ilmestyä tähän.</string>
<string name="device_paired">Laite kytketty pariksi</string>
<string name="device_rename_title">Laitteen nimen muuttaminen</string>
<string name="device_rename_title">Muuta laitteen nimeä</string>
<string name="device_rename_confirm">Muuta nimi</string>
<string name="refresh">Päivitä</string>
<string name="unreachable_description">Tämä pariksi kytketty laite ei ole tavoitettavissa. Tarkista, että se on yhteydessä samaan verkkoon.</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Löydä puhelimeni</string>
<string name="findmyphone_description">Laittaa puhelimen soimaan, jotta voit löytää sen.</string>
<string name="findmyphone_found">Löytyi</string>
<string name="open">Avaa</string>
<string name="close">Sulje</string>
</resources>

View File

@@ -37,6 +37,13 @@
</string-array>
<string name="mousepad_double_default">Droite</string>
<string name="mousepad_triple_default">Milieu</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Périphériques connectés</string>
<string name="category_not_paired_devices">Périphériques disponibles</string>
<string name="category_remembered_devices">Périphériques mémorisés</string>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Mova un dedo na pantalla para mover o cursor. Toque para facer clic, e use dous ou tres dedos para os botóns secundario e central. Prema durante un tempo para arrastrar e soltar.</string>
<string name="mousepad_double_tap_settings_title">Definir a acción de tocar con dous dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a acción de tocar con tres dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do punteiro táctil</string>
<string name="mousepad_scroll_direction_title">Inverter a dirección de desprazamento</string>
<string-array name="mousepad_tap_entries">
<item>Clic dereito</item>
<item>Clic central</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">dereita</string>
<string name="mousepad_triple_default">medio</string>
<string name="mousepad_sensitivity_default">predeterminado</string>
<string-array name="mousepad_sensitivity_entries">
<item>O máis lento</item>
<item>Lento</item>
<item>Predeterminado</item>
<item>Rápido</item>
<item>O mais rápido</item>
</string-array>
<string name="category_connected_devices">Dispositivos conectados</string>
<string name="category_not_paired_devices">Dispositivos dispoñíbeis</string>
<string name="category_remembered_devices">Dispositivos coñecidos</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Configuración do engadido</string>
<string name="device_menu_unpair">Desemparellarse</string>
<string name="device_not_reachable">O dispositivo emparellado está fóra do alcance.</string>
<string name="pair_new_device">Emparellar cun novo dispositivo</string>
<string name="unknown_device">Dispositivo descoñecido</string>
<string name="error_not_reachable">Dispositivo fóra do alcance</string>
<string name="error_already_requested">Xa solicitou emparellarse.</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Atopar o móbil</string>
<string name="findmyphone_description">Reproduce un son de chamada no móbil para que poida atopalo.</string>
<string name="findmyphone_found">Atopado</string>
<string name="open">Abrir</string>
<string name="close">Pechar</string>
</resources>

View File

@@ -25,6 +25,13 @@
<item/>
<item>Nothing</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Csatlakoztatott eszközök</string>
<string name="category_remembered_devices">Megjegyzett eszközök</string>
<string name="plugins_failed_to_load">A bővítményeket nem sikerült betölteni (érintse meg a további információkért):</string>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Muovi un dito sullo schermo per spostare il puntatore del mouse. Tocca per un clic e usa due/tre dita per i pulsanti destro e centrale. Utilizza una pressione lunga per trascinare e rilasciare.</string>
<string name="mousepad_double_tap_settings_title">Imposta azione per il tocco a due dita</string>
<string name="mousepad_triple_tap_settings_title">Imposta azione per il tocco a tre dita</string>
<string name="mousepad_sensitivity_settings_title">Imposta la sensibilità del touchpad</string>
<string name="mousepad_scroll_direction_title">Inverti direzione di scorrimento</string>
<string-array name="mousepad_tap_entries">
<item>Clic destro</item>
<item>Clic centrale</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">destra</string>
<string name="mousepad_triple_default">centro</string>
<string name="mousepad_sensitivity_default">predefinita</string>
<string-array name="mousepad_sensitivity_entries">
<item>Minima</item>
<item>Più veloce</item>
<item>Predefinita</item>
<item>Superiore a predefinita</item>
<item>Massima</item>
</string-array>
<string name="category_connected_devices">Dispositivi connessi</string>
<string name="category_not_paired_devices">Dispositivi disponibili</string>
<string name="category_remembered_devices">Dispositivi memorizzati</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Impostazioni estensioni</string>
<string name="device_menu_unpair">Disassocia</string>
<string name="device_not_reachable">Dispositivo associato non raggiungibile</string>
<string name="pair_new_device">Associa nuovo dispositivo</string>
<string name="unknown_device">Dispositivo sconosciuto</string>
<string name="error_not_reachable">Dispositivo fuori portata</string>
<string name="error_already_requested">Richiesta già inviata</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Trova il mio telefono</string>
<string name="findmyphone_description">Fa squillare questo telefono per trovarlo.</string>
<string name="findmyphone_found">Trovato</string>
<string name="open">Apri</string>
<string name="close">Chiudi</string>
</resources>

View File

@@ -5,6 +5,13 @@
<item>Middle click</item>
<item>Nothing</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string-array name="mpris_time_entries">
<item>10 seconds</item>
<item>20 seconds</item>

View File

@@ -39,6 +39,13 @@
</string-array>
<string name="mousepad_double_default">오른쪽</string>
<string name="mousepad_triple_default">가운데</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">연결된 장치</string>
<string name="category_not_paired_devices">사용 가능한 장치</string>
<string name="category_remembered_devices">기억하는 장치</string>
@@ -143,4 +150,6 @@
<string name="findmyphone_title">내 휴대폰 찾기</string>
<string name="findmyphone_description">소리를 울려서 휴대폰을 찾는 데 도움을 줍니다.</string>
<string name="findmyphone_found">찾았음</string>
<string name="open">열기</string>
<string name="close">닫기</string>
</resources>

View File

@@ -23,6 +23,13 @@
<item>Middle click</item>
<item>Nieko</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Prijungti įrenginiai</string>
<string name="category_not_paired_devices">Pasiekiami įrenginiai</string>
<string name="category_remembered_devices">Įsimintieji įrenginiai</string>
@@ -34,14 +41,11 @@
<string name="error_already_requested">Jau paprašyta suporuoti</string>
<string name="error_already_paired">Įrenginys jau suporuotas</string>
<string name="error_could_not_send_package">Nepavyksta išsiųsti paketo</string>
<string name="error_timed_out">Skirtasis laikas baigėsi</string>
<string name="error_canceled_by_user">Naudotojas atšaukė užduotį</string>
<string name="error_canceled_by_other_peer">Porininkas atšaukė užduotį</string>
<string name="error_invalid_key">Gautas netinkamas raktas</string>
<string name="pair_requested">Paprašyta suporuoti</string>
<string name="incoming_file_text">%1s</string>
<string name="outgoing_file_text">%1s</string>
<string name="sent_file_text">%1s</string>
<string name="sent_file_failed_text">%1s</string>
<string name="tap_to_answer">Norėdami atsakyti, palieskite</string>
<string name="reconnect">Prisijungti iš naujo</string>
<string name="show_keyboard">Rodyti klaviatūrą</string>
@@ -79,7 +83,6 @@
<string name="pair_device_action">Suporuoti naują įrenginį</string>
<string name="unpair_device_action">Atrišti %s</string>
<string name="custom_device_list">Pridėti įrenginį pagal IP</string>
<string name="sftp_internal_storage">Vidinė saugykla</string>
<string name="sftp_all_files">Visi failai</string>
<string name="sftp_sdcard_num">SD kortelė %d</string>
<string name="sftp_sdcard">SD kortelė</string>
@@ -93,6 +96,7 @@
<string name="pairing_description">Čia turėtų pasirodyti to kiti paties tinklo įrenginiai, kuriuose veikia „KDE Connect“</string>
<string name="device_paired">Įrenginys suporuotas</string>
<string name="device_rename_title">Pervadinti įrenginį</string>
<string name="device_rename_confirm">Pervadinti</string>
<string name="refresh">Atnaujinti</string>
<string name="unreachable_description">Šis suporuotas įrenginys nepasiekiamas. Patikrinkite, ar jis prisijungęs prie to paties tinklo.</string>
<string name="pref_plugin_telepathy">Siųsti SMS</string>
@@ -100,4 +104,6 @@
<string name="findmyphone_title">Rasti telefoną</string>
<string name="findmyphone_description">Telefonas skambės, tad galėsite jį rasti.</string>
<string name="findmyphone_found">Radau</string>
<string name="open">Atverti</string>
<string name="close">Užverti</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Veeg met een vinger op het scherm om de muiscursor te verplaatsen. Tik om te klikken en gebruik twee/drie vingers voor rechter en middelste knop. Druk lang voor slepen en loslaten.</string>
<string name="mousepad_double_tap_settings_title">Tikactie met twee vingers instellen</string>
<string name="mousepad_triple_tap_settings_title">Tikactie met drie vingers instellen</string>
<string name="mousepad_sensitivity_settings_title">Gevoeligheid van touchpad instellen</string>
<string name="mousepad_scroll_direction_title">Schuifrichting omdraaien</string>
<string-array name="mousepad_tap_entries">
<item>Rechter muisklik</item>
<item>Middelste muisklik</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">rechts</string>
<string name="mousepad_triple_default">midden</string>
<string name="mousepad_sensitivity_default">standaard</string>
<string-array name="mousepad_sensitivity_entries">
<item>Langzaamst</item>
<item>Langzaam</item>
<item>Standaard</item>
<item>Boven standaard</item>
<item>Snelste</item>
</string-array>
<string name="category_connected_devices">Verbonden apparaten</string>
<string name="category_not_paired_devices">Beschikbare apparaten</string>
<string name="category_remembered_devices">Onthouden apparaten</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Plugin-instellingen</string>
<string name="device_menu_unpair">Paar uit elkaar halen</string>
<string name="device_not_reachable">Gepaard apparaat niet bereikbaar</string>
<string name="pair_new_device">Nieuw apparaat paren</string>
<string name="unknown_device">Onbekend apparaat</string>
<string name="error_not_reachable">Apparaat niet bereikbaar</string>
<string name="error_already_requested">Paar maken is al gevraagd</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Zoek mijn telefoon</string>
<string name="findmyphone_description">Laat deze telefoon bellen zodat u het kunt vinden.</string>
<string name="findmyphone_found">Gevonden</string>
<string name="open">Openen</string>
<string name="close">Sluiten</string>
</resources>

View File

@@ -32,13 +32,23 @@
<string name="mousepad_info">Przesuń palcem po ekranie, aby przesunąć wskaźnik myszy. Stuknij, aby wywołać naciśniecie lewym przyciskiem myszy i użyj dwóch/trzech palców, aby wywołać naciśniecie prawym i środkowym przyciskiem myszy. Przyciśnij na dłużej, aby przeciągnąć i upuścić.</string>
<string name="mousepad_double_tap_settings_title">Ustaw działanie po dwukrotnym stuknięciu palcem</string>
<string name="mousepad_triple_tap_settings_title">Ustaw działanie po trzykrotnym stuknięciu palcem</string>
<string name="mousepad_sensitivity_settings_title">Ustaw czułość gładzika</string>
<string name="mousepad_scroll_direction_title">Odwróć stronę przewijania</string>
<string-array name="mousepad_tap_entries">
<item>Naciśnięcie prawym</item>
<item>Naciśnięcie środkowym</item>
<item>Kliknięcie prawym</item>
<item>Kliknięcie środkowym</item>
<item>Nic</item>
</string-array>
<string name="mousepad_double_default">prawo</string>
<string name="mousepad_triple_default">środek</string>
<string name="mousepad_sensitivity_default">domyślne</string>
<string-array name="mousepad_sensitivity_entries">
<item>Najmniejsza</item>
<item>Ponad najmniejszą</item>
<item>Domyślna</item>
<item>Powyżej domyślnej</item>
<item>Największa</item>
</string-array>
<string name="category_connected_devices">Podłączone urządzenia</string>
<string name="category_not_paired_devices">Dostępne urządzenia</string>
<string name="category_remembered_devices">Zapamiętane urządzenia</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Ustawienia wtyczki</string>
<string name="device_menu_unpair">Odparuj</string>
<string name="device_not_reachable">Sparowane urządzenie nieosiągalne</string>
<string name="pair_new_device">Sparuj nowe urządzenie</string>
<string name="unknown_device">Nieznane urządzenie</string>
<string name="error_not_reachable">Urządzenie nieosiągalne</string>
<string name="error_already_requested">Już zażądano parowania</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Znajdź mój telefon</string>
<string name="findmyphone_description">Dzwoni na ten telefon, tak abyś mógł go znaleźć.</string>
<string name="findmyphone_found">Znaleziony</string>
<string name="open">Otwórz</string>
<string name="close">Zamknij</string>
</resources>

View File

@@ -1,15 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Notificação telefônica</string>
<string name="pref_plugin_telephony">Notificação de telefonia</string>
<string name="pref_plugin_telephony_desc">Envia notificações de SMS e chamadas</string>
<string name="pref_plugin_battery">Relatório da bateria</string>
<string name="pref_plugin_battery_desc">Informa o status da bateria periodicamente</string>
<string name="pref_plugin_battery_desc">Informação periódica do status da bateria</string>
<string name="pref_plugin_sftp">Exposição do sistema de arquivos</string>
<string name="pref_plugin_sftp_desc">Permite navegar remotamente pelo sistema de arquivos telefone</string>
<string name="pref_plugin_clipboard">Sincronizar área de transferência</string>
<string name="pref_plugin_sftp_desc">Navegação remota pelo sistema de arquivos do telefone</string>
<string name="pref_plugin_clipboard">Sincronização da área de transferência</string>
<string name="pref_plugin_clipboard_desc">Compartilha o conteúdo da área de transferência</string>
<string name="pref_plugin_mousepad">Introdução de dados remota</string>
<string name="pref_plugin_mousepad_desc">Usar seu telefone como mouse ou teclado</string>
<string name="pref_plugin_mousepad_desc">Use seu telefone como mouse ou teclado</string>
<string name="pref_plugin_mpris">Controle multimídia</string>
<string name="pref_plugin_mpris_desc">Controla áudio e vídeo a partir do seu telefone</string>
<string name="pref_plugin_runcommand">Executar comando</string>
@@ -19,7 +19,7 @@
<string name="pref_plugin_notifications">Sincronização de notificações</string>
<string name="pref_plugin_notifications_desc">Acesse suas notificações a partir de outros dispositivos</string>
<string name="pref_plugin_sharereceiver">Compartilhar e receber</string>
<string name="pref_plugin_sharereceiver_desc">Compartilhar arquivos e URLs entre os dispositivos</string>
<string name="pref_plugin_sharereceiver_desc">Compartilha arquivos e URLs entre os dispositivos</string>
<string name="plugin_not_available">Esta funcionalidade não está disponível na sua versão do Android</string>
<string name="device_list_empty">Sem dispositivos</string>
<string name="ok">OK</string>
@@ -29,9 +29,11 @@
<string name="send_ping">Enviar ping</string>
<string name="open_mpris_controls">Controle multimídia</string>
<string name="open_mousepad">Introdução de dados remota</string>
<string name="mousepad_info">Mova um dedo pelo tela para mover o ponteiro do mouse. Dê um toque para clicar e use dois/três dedos para os botões da direita e do meio. Use uma pressão longa para arrastar e soltar.</string>
<string name="mousepad_info">Mova um dedo pela tela para mover o ponteiro do mouse. Dê um toque para clicar e use dois/três dedos para os botões da direita e do meio. Use uma pressão longa para arrastar e soltar.</string>
<string name="mousepad_double_tap_settings_title">Definir a ação do toque com dois dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a ação do toque com três dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do touchpad</string>
<string name="mousepad_scroll_direction_title">Direção de rolagem inversa</string>
<string-array name="mousepad_tap_entries">
<item>Botão direito</item>
<item>Botão do meio</item>
@@ -39,13 +41,22 @@
</string-array>
<string name="mousepad_double_default">direita</string>
<string name="mousepad_triple_default">meio</string>
<string name="mousepad_sensitivity_default">padrão</string>
<string-array name="mousepad_sensitivity_entries">
<item>Mais lento</item>
<item>Ainda mais lento</item>
<item>Padrão</item>
<item>Acima do padrão</item>
<item>Mais rápido</item>
</string-array>
<string name="category_connected_devices">Dispositivos conectados</string>
<string name="category_not_paired_devices">Dispositivos disponíveis</string>
<string name="category_remembered_devices">Dispositivos lembrados</string>
<string name="plugins_failed_to_load">Plugins não carregados (toque para mais informações):</string>
<string name="device_menu_plugins">Configurações do plugin</string>
<string name="device_menu_plugins">Configuração dos plugins</string>
<string name="device_menu_unpair">Cancelar emparelhamento</string>
<string name="device_not_reachable">O dispositivo pareado está inacessível</string>
<string name="pair_new_device">Emparelhar novo dispositivo</string>
<string name="unknown_device">Dispositivo desconhecido</string>
<string name="error_not_reachable">Dispositivo inacessível</string>
<string name="error_already_requested">O emparelhamento já foi solicitado</string>
@@ -55,8 +66,8 @@
<string name="error_canceled_by_user">Cancelado pelo usuário</string>
<string name="error_canceled_by_other_peer">Cancelado pelo outro dispositivo</string>
<string name="error_invalid_key">Chave inválida recebida</string>
<string name="pair_requested">Emparelhamento solicitado</string>
<string name="pairing_request_from">Emparelhando solicitação de %1s</string>
<string name="pair_requested">Solicitação de emparelhamento</string>
<string name="pairing_request_from">Emparelhamento solicitado por %1s</string>
<string name="received_url_title">Link recebido de %1s</string>
<string name="received_url_text">Toque para abrir o \'%1s\'</string>
<string name="incoming_file_title">Arquivo recebido de %1s</string>
@@ -111,12 +122,12 @@
<string name="shareplugin_text_saved">Texto recebido e salvo na área de transferência</string>
<string name="custom_devices_settings">Lista de dispositivos personalizada</string>
<string name="pair_device_action">Emparelhar um novo dispositivo</string>
<string name="unpair_device_action">Desemparelhar o %s</string>
<string name="unpair_device_action">Desemparelhar %s</string>
<string name="custom_device_list">Adicionar dispositivos pelo IP</string>
<string name="share_notification_preference">Notificações barulhentas</string>
<string name="share_notification_preference_summary">Vibrar e tocar um som ao receber um arquivo</string>
<string name="share_notification_preference">Notificações sonoras</string>
<string name="share_notification_preference_summary">Vibra e reproduz um som ao receber um arquivo</string>
<string name="title_activity_notification_filter">Filtro de notificações</string>
<string name="filter_apps_info">As notificações serão sincronizadas para os aplicativos selecionados.</string>
<string name="filter_apps_info">As notificações dos aplicativos selecionados serão sincronizadas.</string>
<string name="sftp_internal_storage">Armazenamento interno</string>
<string name="sftp_all_files">Todos os arquivos</string>
<string name="sftp_sdcard_num">Cartão SD %d</string>
@@ -130,7 +141,7 @@
<string name="mpris_player_on_device">%1$s em %2$s</string>
<string name="send_files">Enviar arquivos</string>
<string name="pairing_title">Dispositivos do KDE Connect</string>
<string name="pairing_description">Os outros dispositivos executando o KDE Connect na mesma rede devem aparecer aqui.</string>
<string name="pairing_description">Outros dispositivos executando o KDE Connect na mesma rede devem aparecer aqui.</string>
<string name="device_paired">Dispositivo emparelhado</string>
<string name="device_rename_title">Renomear dispositivo</string>
<string name="device_rename_confirm">Renomear</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Encontrar meu telefone</string>
<string name="findmyphone_description">Faça este telefone tocar para encontrá-lo.</string>
<string name="findmyphone_found">Encontrado</string>
<string name="open">Abrir</string>
<string name="close">Fechar</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Mova um dedo pelo ecrã para mover o cursor do rato. Dê um toque para carregar no botão esquerdo e use dois/três dedos para os botões direito e do meio. Use uma pressão longa para arrastar e largar.</string>
<string name="mousepad_double_tap_settings_title">Definir a acção do toque com dois dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a acção do toque com três dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do rato por toque</string>
<string name="mousepad_scroll_direction_title">Direcção de Deslocamento Inversa</string>
<string-array name="mousepad_tap_entries">
<item>Botão direito</item>
<item>Botão do meio</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">direita</string>
<string name="mousepad_triple_default">meio</string>
<string name="mousepad_sensitivity_default">predefinição</string>
<string-array name="mousepad_sensitivity_entries">
<item>Mais Lento</item>
<item>Ainda Mais Lento</item>
<item>Predefinição</item>
<item>Acima da Predefinição</item>
<item>Mais Rápido</item>
</string-array>
<string name="category_connected_devices">Dispositivos ligados</string>
<string name="category_not_paired_devices">Dispositivos disponíveis</string>
<string name="category_remembered_devices">Dispositivos recordados</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Configuração do \'plugin\'</string>
<string name="device_menu_unpair">Desemparelhar</string>
<string name="device_not_reachable">O dispositivo emparelhado está inacessível</string>
<string name="pair_new_device">Emparelhar um novo dispositivo</string>
<string name="unknown_device">Dispositivo desconhecido</string>
<string name="error_not_reachable">Dispositivo inacessível</string>
<string name="error_already_requested">O emparelhamento já foi pedido</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Descobrir o Meu Telefone</string>
<string name="findmyphone_description">Toca este telefone para que o possa encontrar.</string>
<string name="findmyphone_found">Encontrado</string>
<string name="open">Abrir</string>
<string name="close">Fechar</string>
</resources>

View File

@@ -25,6 +25,13 @@
<item>Middle click</item>
<item>Nothing</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Dispozitive conectate</string>
<string name="category_remembered_devices">Dispozitive memorizate</string>
<string name="plugins_failed_to_load">Încărcarea extensiilor a eșuat (atingeți pentru mai multe informații):</string>

View File

@@ -39,6 +39,13 @@
</string-array>
<string name="mousepad_double_default">Нажатие правой кнопки</string>
<string name="mousepad_triple_default">Нажатие средней кнопки</string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">Подключённые устройства</string>
<string name="category_not_paired_devices">Доступные устройства</string>
<string name="category_remembered_devices">Известные устройства</string>
@@ -143,4 +150,6 @@
<string name="findmyphone_title">Поиск телефона</string>
<string name="findmyphone_description">Подача звукового сигнала на телефоне, чтобы вы могли его найти.</string>
<string name="findmyphone_found">Найден</string>
<string name="open">Открыть</string>
<string name="close">Закрыть</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Posúvajte prst na obrazovke na posun kurzora. Ťuknutie vyvolá klik a použite dva/tri prsty pre pravé a stredné tlačidlo. Použite dlhé stlačenie pre drag and drop.</string>
<string name="mousepad_double_tap_settings_title">Nastaviť akciu dvoch prstov</string>
<string name="mousepad_triple_tap_settings_title">Nastaviť akciu troch prstov</string>
<string name="mousepad_sensitivity_settings_title">Nastaviť citlivosť touchpadu</string>
<string name="mousepad_scroll_direction_title">Obrátený smer rolovania</string>
<string-array name="mousepad_tap_entries">
<item>Kliknutie pravým tlačidlom</item>
<item>Stredný klik</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">vpravo</string>
<string name="mousepad_triple_default">stred</string>
<string name="mousepad_sensitivity_default">predvolené</string>
<string-array name="mousepad_sensitivity_entries">
<item>Najpomalšie</item>
<item>Nad najpomalším</item>
<item>Predvolené</item>
<item>Nad priemerným</item>
<item>Najrýchlejšie</item>
</string-array>
<string name="category_connected_devices">Pripojené zariadenia</string>
<string name="category_not_paired_devices">Dostupné zariadenia</string>
<string name="category_remembered_devices">Zapamätané zariadenia</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Nastavenia pluginu</string>
<string name="device_menu_unpair">Odpárovať</string>
<string name="device_not_reachable">Spárované zariadenie nedostupné</string>
<string name="pair_new_device">Spárovať nové zariadenie</string>
<string name="unknown_device">Neznáme zariadenie</string>
<string name="error_not_reachable">Zariadenie nedostupné</string>
<string name="error_already_requested">Spárovanie už vyžiadané</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Nájsť môj telefón</string>
<string name="findmyphone_description">Prezvoní vaše zariadenie, aby ste ho našli.</string>
<string name="findmyphone_found">Nájdené</string>
<string name="open">Otvoriť</string>
<string name="close">Zavrieť</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Flytta fingret på skärmen för att röra muspekaren. Rör för att klicka, och använd två eller tre fingrar för höger- och mittenknapparna. Använd en längre beröring för drag och släpp.</string>
<string name="mousepad_double_tap_settings_title">Ställ in åtgärd vid två fingerberöringar</string>
<string name="mousepad_triple_tap_settings_title">Ställ in åtgärd vid tre fingerberöringar</string>
<string name="mousepad_sensitivity_settings_title">Ställ in tryckplattans känslighet</string>
<string name="mousepad_scroll_direction_title">Omvänd rullningsriktning</string>
<string-array name="mousepad_tap_entries">
<item>Högerklick</item>
<item>Mittenklick</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">höger</string>
<string name="mousepad_triple_default">mitten</string>
<string name="mousepad_sensitivity_default">normal</string>
<string-array name="mousepad_sensitivity_entries">
<item>Långsammaste</item>
<item>Ovanför långsammaste</item>
<item>Normal</item>
<item>Ovanför normal</item>
<item>Snabbaste</item>
</string-array>
<string name="category_connected_devices">Anslutna apparater</string>
<string name="category_not_paired_devices">Tillgängliga apparater</string>
<string name="category_remembered_devices">Ihågkomna apparater</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Inställningar av insticksprogram</string>
<string name="device_menu_unpair">Ta bort ihopparning</string>
<string name="device_not_reachable">Ihopparad apparat kan inte nås</string>
<string name="pair_new_device">Para ihop ny apparat</string>
<string name="unknown_device">Okänd apparat</string>
<string name="error_not_reachable">Apparaten kan inte nås</string>
<string name="error_already_requested">Ihopparning redan begärd</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Hitta min telefon</string>
<string name="findmyphone_description">Ringer till telefonen så att du kan hitta den.</string>
<string name="findmyphone_found">Hittade den</string>
<string name="open">Öppna</string>
<string name="close">Stäng</string>
</resources>

View File

@@ -32,6 +32,8 @@
<string name="mousepad_info">Проведіть по екрану пальцем, щоб пересунути вказівник миші. Дотик одним пальцем означатиме клацання, дотиком двома або трьома пальцями можна імітувати праву і середню кнопки. Для перетягування зі скиданням скористайтеся тривалим натисканням.</string>
<string name="mousepad_double_tap_settings_title">Встановлення дії для торкання двома пальцями</string>
<string name="mousepad_triple_tap_settings_title">Встановлення дії для торкання трьома пальцями</string>
<string name="mousepad_sensitivity_settings_title">Встановити чутливість сенсорної панелі</string>
<string name="mousepad_scroll_direction_title">Зворотний напрямок гортання</string>
<string-array name="mousepad_tap_entries">
<item>Клацання правою</item>
<item>Клацання середньою</item>
@@ -39,6 +41,14 @@
</string-array>
<string name="mousepad_double_default">права</string>
<string name="mousepad_triple_default">середня</string>
<string name="mousepad_sensitivity_default">типва</string>
<string-array name="mousepad_sensitivity_entries">
<item>Найповільніший</item>
<item>Швидший за найповільніший</item>
<item>Типовий</item>
<item>Швидший за типовий</item>
<item>Найшвидший</item>
</string-array>
<string name="category_connected_devices">З’єднані пристрої</string>
<string name="category_not_paired_devices">Доступні пристрої</string>
<string name="category_remembered_devices">Відомі пристрої</string>
@@ -46,6 +56,7 @@
<string name="device_menu_plugins">Параметри додатків</string>
<string name="device_menu_unpair">Скасувати пов’язування</string>
<string name="device_not_reachable">Немає доступу до пов’язаного пристрою</string>
<string name="pair_new_device">Пов’язати новий пристрій</string>
<string name="unknown_device">Невідомий пристрій</string>
<string name="error_not_reachable">Немає доступу до пристрою</string>
<string name="error_already_requested">Запит щодо пов’язування вже надіслано</string>
@@ -143,4 +154,6 @@
<string name="findmyphone_title">Знайти телефон</string>
<string name="findmyphone_description">Відтворити дзвінок, щоб телефон було простіше знайти.</string>
<string name="findmyphone_found">Знайдено</string>
<string name="open">Відкрити</string>
<string name="close">Закрити</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectThemeBase.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>

View File

@@ -39,6 +39,13 @@
</string-array>
<string name="mousepad_double_default"></string>
<string name="mousepad_triple_default"></string>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string name="category_connected_devices">已连接设备</string>
<string name="category_not_paired_devices">可用设备</string>
<string name="category_remembered_devices">已记住设备</string>
@@ -46,6 +53,7 @@
<string name="device_menu_plugins">插件设置</string>
<string name="device_menu_unpair">取消配对</string>
<string name="device_not_reachable">配对设备不可及</string>
<string name="pair_new_device">配对新设备</string>
<string name="unknown_device">未知设备</string>
<string name="error_not_reachable">设备不可及</string>
<string name="error_already_requested">已在请求配对</string>
@@ -143,4 +151,6 @@
<string name="findmyphone_title">找到我的手机</string>
<string name="findmyphone_description">让手机响铃从而找到它</string>
<string name="findmyphone_found">找到</string>
<string name="open">打开</string>
<string name="close">关闭</string>
</resources>

View File

@@ -14,7 +14,7 @@
<string name="pref_plugin_mpris">Multimedia controls</string>
<string name="pref_plugin_mpris_desc">Control audio/video from your phone</string>
<string name="pref_plugin_runcommand">Run Command</string>
<string name="pref_plugin_runcommand_desc">Runs a command on your system</string>
<string name="pref_plugin_runcommand_desc">Trigger remote commands from your phone</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Send and receive pings</string>
<string name="pref_plugin_notifications">Notification sync</string>
@@ -33,8 +33,12 @@
<string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use a long press to drag\'n drop.</string>
<string name="mousepad_double_tap_settings_title">Set two finger tap action</string>
<string name="mousepad_triple_tap_settings_title">Set three finger tap action</string>
<string name="mousepad_sensitivity_settings_title">Set touchpad sensitivity</string>
<string name="mousepad_double_tap_key" translatable="false">mousepad_double_tap_key</string>
<string name="mousepad_triple_tap_key" translatable="false">mousepad_triple_tap_key</string>
<string name="mousepad_sensitivity_key" translatable="false">mousepad_sensitivity_key</string>
<string name="mousepad_scroll_direction_title">Reverse Scrolling Direction</string>
<string name="mousepad_scroll_direction" translatable="false">mousepad_scroll_direction</string>
<string-array name="mousepad_tap_entries">
<item>Right click</item>
<item>Middle click</item>
@@ -42,11 +46,26 @@
</string-array>
<string name="mousepad_double_default">right</string>
<string name="mousepad_triple_default">middle</string>
<string name="mousepad_sensitivity_default">default</string>
<string-array name="mousepad_tap_values" translatable="false">
<item>right</item>
<item>middle</item>
<item>none</item>
</string-array>
<string-array name="mousepad_sensitivity_entries">
<item>Slowest</item>
<item>Above Slowest</item>
<item>Default</item>
<item>Above Default</item>
<item>Fastest</item>
</string-array>
<string-array name="mousepad_sensitivity_values" translatable="false">
<item>slowest</item>
<item>aboveSlowest</item>
<item>default</item>
<item>aboveDefault</item>
<item>fastest</item>
</string-array>
<string name="category_connected_devices">Connected devices</string>
<string name="category_not_paired_devices">Available devices</string>
<string name="category_remembered_devices">Remembered devices</string>
@@ -64,6 +83,10 @@
<string name="error_canceled_by_user">Canceled by user</string>
<string name="error_canceled_by_other_peer">Canceled by other peer</string>
<string name="error_invalid_key">Invalid key received</string>
<string name="encryption_info_title">Encryption Info</string>
<string name="encryption_info_msg_no_ssl">The other device doesn\'t use a recent version of KDE Connect, using the legacy encryption method.</string>
<string name="my_device_fingerprint">SHA1 fingerprint of your device certificate is : </string>
<string name="remote_device_fingerprint">SHA1 fingerprint of remote device certificate is : </string>
<string name="pair_requested">Pair requested</string>
<string name="pairing_request_from">Pairing request from %1s</string>
<string name="received_url_title">Received link from %1s</string>
@@ -159,7 +182,10 @@
<string name="pref_plugin_telepathy">Send SMS</string>
<string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string>
<string name="plugin_not_supported">This plugin is not supported by the device</string>
<string name="findmyphone_title">Find My Phone</string>
<string name="findmyphone_title">Find my phone</string>
<string name="findmyphone_description">Rings this phone so you can find it.</string>
<string name="findmyphone_found">Found</string>
<string name="open">Open</string>
<string name="close">Close</string>
</resources>

View File

@@ -4,7 +4,7 @@
<color name="accent">#4ebffa</color>
<!-- NoActionBar because we use a Toolbar widget as ActionBar -->
<style name="KdeConnectTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="KdeConnectThemeBase" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- The three colors used by system widgets, according to https://chris.banes.me/2014/10/17/appcompat-v21/ -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
@@ -13,9 +13,15 @@
<item name="android:textColor">@android:color/black</item>
</style>
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectTheme">
<style name="KdeConnectThemeBase.NoActionBar" parent="KdeConnectThemeBase">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="KdeConnectTheme" parent="KdeConnectThemeBase">
</style>
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectThemeBase.NoActionBar">
</style>
</resources>

View File

@@ -21,4 +21,18 @@
android:entryValues="@array/mousepad_tap_values"
android:defaultValue="@string/mousepad_triple_default" />
<ListPreference
android:id="@+id/mousepad_sensitivity_preference"
android:key="@string/mousepad_sensitivity_key"
android:title="@string/mousepad_sensitivity_settings_title"
android:summary="%s"
android:entries="@array/mousepad_sensitivity_entries"
android:entryValues="@array/mousepad_sensitivity_values"
android:defaultValue="@string/mousepad_sensitivity_default" />
<CheckBoxPreference
android:id="@+id/mousepad_scroll_preference"
android:key="@string/mousepad_scroll_direction"
android:title="@string/mousepad_scroll_direction_title"
android:defaultValue="false" />
</PreferenceScreen>

View File

@@ -20,6 +20,8 @@
package org.kde.kdeconnect.Backends;
import android.content.Context;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
@@ -30,9 +32,7 @@ import java.util.ArrayList;
public abstract class BaseLink {
public enum ConnectionStarted {
Locally, Remotely;
};
protected final Context context;
public interface PackageReceiver {
void onPackageReceived(NetworkPackage np);
@@ -43,17 +43,16 @@ public abstract class BaseLink {
private final ArrayList<PackageReceiver> receivers = new ArrayList<>();
protected PrivateKey privateKey;
protected ConnectionStarted connectionSource; // If the other device sent me a broadcast,
// I should not close the connection with it
// because it's probably trying to find me and
// potentially ask for pairing.
protected BaseLink(String deviceId, BaseLinkProvider linkProvider, ConnectionStarted connectionSource) {
protected BaseLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
this.context = context;
this.linkProvider = linkProvider;
this.deviceId = deviceId;
this.connectionSource = connectionSource;
}
/* To be implemented by each link for pairing handlers */
public abstract String getName();
public abstract BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback);
public String getDeviceId() {
return deviceId;
}
@@ -66,8 +65,9 @@ public abstract class BaseLink {
return linkProvider;
}
public ConnectionStarted getConnectionSource() {
return connectionSource;
//The daemon will periodically destroy unpaired links if this returns false
public boolean linkShouldBeKeptAlive() {
return false;
}
public void addPackageReceiver(PackageReceiver pr) {

View File

@@ -20,6 +20,9 @@
package org.kde.kdeconnect.Backends;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.NetworkPackage;
import java.util.ArrayList;

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
import java.util.Timer;
/**
* This class separates the pairing interface for each type of link.
* Since different links can pair via different methods, like for LanLink certificate and public key should be shared,
* for Bluetooth link they should be paired via bluetooth etc.
* Each "Device" instance maintains a hash map for these pairing handlers so that there can be single pairing handler per
* per link type per device.
* Pairing handler keeps information about device, latest link, and pair status of the link
* During first pairing process, the pairing process is nearly same as old process.
* After that if any one of the link is paired, then we can say that device is paired, so new link will pair automatically
*/
public abstract class BasePairingHandler {
protected enum PairStatus{
NotPaired,
Requested,
RequestedByPeer,
Paired
}
public interface PairingHandlerCallback {
void incomingRequest();
void pairingDone();
void pairingFailed(String error);
void unpaired();
}
protected Device mDevice;
protected BaseLink mBaseLink;
protected PairStatus mPairStatus;
protected PairingHandlerCallback mCallback;
protected Timer mPairingTimer;
public BasePairingHandler(Device device, PairingHandlerCallback callback) {
this.mDevice = device;
this.mCallback = callback;
}
public void setLink(BaseLink baseLink) {
this.mBaseLink = baseLink;
}
public boolean isPaired() {
return mPairStatus == PairStatus.Paired;
}
public boolean isPairRequested() {
return mPairStatus == PairStatus.Requested;
}
public boolean isPairRequestedByPeer() {
return mPairStatus == PairStatus.RequestedByPeer;
}
/* To be implemented by respective pairing handler */
public abstract NetworkPackage createPairPackage();
public abstract void packageReceived(NetworkPackage np) throws Exception;
public abstract void requestPairing();
public abstract void acceptPairing();
public abstract void rejectPairing();
public abstract void pairingDone();
public abstract void unpair();
}

View File

@@ -20,14 +20,18 @@
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPackage;
import java.io.IOException;
@@ -39,32 +43,81 @@ import java.net.Socket;
import java.nio.channels.NotYetConnectedException;
import java.security.PublicKey;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
public class LanLink extends BaseLink {
private IoSession session = null;
public enum ConnectionStarted {
Locally, Remotely;
};
protected ConnectionStarted connectionSource; // If the other device sent me a broadcast,
// I should not close the connection with it
// because it's probably trying to find me and
// potentially ask for pairing.
private Channel channel = null;
private boolean onSsl = false;
@Override
public void disconnect() {
closeSocket();
super.disconnect();
}
//Returns the old channel
public Channel reset(Channel channel, ConnectionStarted connectionSource, boolean onSsl) {
Channel oldChannel = this.channel;
this.channel = channel;
this.connectionSource = connectionSource;
return oldChannel;
}
public void closeSocket() {
if (session == null) {
if (channel == null) {
Log.e("KDE/LanLink", "Not yet connected");
return;
}
session.close(true);
channel.close();
}
public LanLink(IoSession session, String deviceId, BaseLinkProvider linkProvider, ConnectionStarted connectionSource) {
super(deviceId, linkProvider, connectionSource);
this.session = session;
public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, Channel channel, ConnectionStarted connectionSource, boolean onSsl) {
super(context, deviceId, linkProvider);
reset(channel, connectionSource, onSsl);
}
@Override
public String getName() {
return "LanLink";
}
@Override
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
return new LanPairingHandler(device, callback);
}
@Override
public void addPackageReceiver(PackageReceiver pr) {
super.addPackageReceiver(pr);
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(getDeviceId());
if (device == null) return;
if (!device.isPaired()) return;
// If the device is already paired due to other link, just send a pairing request to get required attributes for this link
}
});
}
//Blocking, do not call from main thread
private void sendPackageInternal(NetworkPackage np, final Device.SendPackageStatusCallback callback, PublicKey key) {
if (session == null) {
if (channel == null) {
Log.e("KDE/sendPackage", "Not yet connected");
callback.sendFailure(new NotYetConnectedException());
return;
@@ -75,7 +128,7 @@ public class LanLink extends BaseLink {
//Prepare socket for the payload
final ServerSocket server;
if (np.hasPayload()) {
server = openTcpSocketOnFreePort();
server = openTcpSocketOnFreePort(context, getDeviceId(), onSsl);
JSONObject payloadTransferInfo = new JSONObject();
payloadTransferInfo.put("port", server.getLocalPort());
np.setPayloadTransferInfo(payloadTransferInfo);
@@ -85,18 +138,18 @@ public class LanLink extends BaseLink {
//Encrypt if key provided
if (key != null) {
np = np.encrypt(key);
np = RsaHelper.encrypt(np, key);
}
//Send body of the network package
WriteFuture future = session.write(np.serialize());
future.awaitUninterruptibly();
if (!future.isWritten()) {
//Log.e("KDE/sendPackage", "!future.isWritten()");
callback.sendFailure(future.getException());
ChannelFuture future = channel.writeAndFlush(np.serialize()).sync();
if (!future.isSuccess()) {
Log.e("KDE/sendPackage", "!future.isWritten()");
callback.sendFailure(future.cause());
return;
}
//Send payload
if (server != null) {
OutputStream socket = null;
@@ -158,18 +211,21 @@ public class LanLink extends BaseLink {
//Blocking, do not call from main thread
@Override
public void sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
sendPackageInternal(np, callback, key);
if (onSsl) {
sendPackageInternal(np, callback, null); // No need to encrypt
}else {
sendPackageInternal(np, callback, key);
}
}
public void injectNetworkPackage(NetworkPackage np) {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
try {
np = np.decrypt(privateKey);
np = RsaHelper.decrypt(np, privateKey);
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/onPackageReceived","Exception reading the key needed to decrypt the package");
Log.e("KDE/onPackageReceived","Exception decrypting the package");
}
}
@@ -178,9 +234,16 @@ public class LanLink extends BaseLink {
Socket socket = null;
try {
socket = new Socket();
// Use ssl if existing link is on ssl
if (onSsl) {
SSLContext sslContext = SslHelper.getSslContext(context, getDeviceId(), true);
socket = sslContext.getSocketFactory().createSocket();
} else {
socket = new Socket();
}
int tcpPort = np.getPayloadTransferInfo().getInt("port");
InetSocketAddress address = (InetSocketAddress)session.getRemoteAddress();
InetSocketAddress address = (InetSocketAddress)channel.remoteAddress();
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
np.setPayload(socket.getInputStream(), np.getPayloadSize());
} catch (Exception e) {
@@ -194,8 +257,16 @@ public class LanLink extends BaseLink {
packageReceived(np);
}
static ServerSocket openTcpSocketOnFreePort(Context context, String deviceId, boolean useSsl) throws IOException {
if (useSsl) {
return openSecureServerSocket(context, deviceId);
} else {
return openUnsecureSocketOnFreePort();
}
}
static ServerSocket openTcpSocketOnFreePort() throws IOException {
static ServerSocket openUnsecureSocketOnFreePort() throws IOException {
boolean success = false;
int tcpPort = 1739;
ServerSocket candidateServer = null;
@@ -217,4 +288,48 @@ public class LanLink extends BaseLink {
return candidateServer;
}
static ServerSocket openSecureServerSocket(Context context, String deviceId) throws IOException{
boolean success = false;
int tcpPort = 1739;
SSLContext tlsContext = SslHelper.getSslContext(context, deviceId, true);
SSLServerSocketFactory sslServerSocketFactory = tlsContext.getServerSocketFactory();
ServerSocket candidateServer = null;
while(!success) {
try {
candidateServer = sslServerSocketFactory.createServerSocket();
candidateServer.bind(new InetSocketAddress(tcpPort));
success = true;
Log.i("LanLink", "Using port "+tcpPort);
} catch(IOException e) {
//Log.e("LanLink", "Exception opening serversocket: "+e);
tcpPort++;
if (tcpPort >= 1764) {
Log.e("LanLink", "No more ports available");
throw e;
}
}
}
return candidateServer;
}
@Override
public boolean linkShouldBeKeptAlive() {
//We keep the remotely initiated connections, since the remotes require them if they want to request
//pairing to us, or connections that are already paired. TODO: Keep connections in the process of pairing
if (connectionSource == ConnectionStarted.Remotely) {
return true;
}
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
if (preferences.contains(getDeviceId())) {
return true; //Already paired
}
return false;
}
}

View File

@@ -21,66 +21,99 @@
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.util.LongSparseArray;
import android.util.Base64;
import android.util.Log;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
public class LanLinkProvider extends BaseLinkProvider {
public static final String KEY_CUSTOM_DEVLIST_PREFERENCE = "device_list_preference";
private final static int port = 1714;
private static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
private final Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<>();
private final LongSparseArray<LanLink> nioSessions = new LongSparseArray<>();
private final LongSparseArray<NioSocketConnector> nioConnectors = new LongSparseArray<>();
private NioSocketAcceptor tcpAcceptor = null;
private NioDatagramAcceptor udpAcceptor = null;
private final HashMap<String, LanLink> visibleComputers = new HashMap<>(); //Links by device id
private final LongSparseArray<LanLink> nioLinks = new LongSparseArray<>(); //Links by channel id
private final IoHandler tcpHandler = new IoHandlerAdapter() {
private EventLoopGroup bossGroup, workerGroup, udpGroup, clientGroup;
private TcpHandler tcpHandler = new TcpHandler();
private UdpHandler udpHandler = new UdpHandler();
// To prevent infinte loop if both device can only broadcast identity package but cannot connect via TCO
private ArrayList<String> reverseConnectionBlackList = new ArrayList<>();
private Timer reverseConnectionTimer;
@ChannelHandler.Sharable
private class TcpHandler extends SimpleChannelInboundHandler<String>{
@Override
public void sessionClosed(IoSession session) throws Exception {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
// Close channel for any sudden exception
ctx.channel().close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// Called after a long time if remote device closes session unexpectedly, like wifi off
try {
long id = session.getId();
final LanLink brokenLink = nioSessions.get(id);
NioSocketConnector connector = nioConnectors.get(id);
if (connector != null) {
connector.dispose();
nioConnectors.remove(id);
}
Channel channel = ctx.channel();
final LanLink brokenLink = nioLinks.get(channel.hashCode());
if (brokenLink != null) {
nioSessions.remove(id);
//Log.i("KDE/LanLinkProvider", "nioSessions.size(): " + nioSessions.size() + " (-)");
nioLinks.remove(channel.hashCode());
//Log.i("KDE/LanLinkProvider", "nioLinks.size(): " + nioLinks.size() + " (-)");
try {
brokenLink.closeSocket();
} catch (Exception e) {
@@ -95,7 +128,7 @@ public class LanLinkProvider extends BaseLinkProvider {
new Thread(new Runnable() {
@Override
public void run() {
//Wait a bit before emiting connectionLost, in case the same device re-appears
//Wait a bit before emitting connectionLost, in case the same device re-appears
try {
Thread.sleep(200);
} catch (InterruptedException e) {
@@ -106,147 +139,257 @@ public class LanLinkProvider extends BaseLinkProvider {
}).start();
}
} catch (Exception e) { //If we don't catch it here, Mina will swallow it :/
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/LanLinkProvider", "sessionClosed exception");
Log.e("KDE/LanLinkProvider", "channelInactive exception");
}
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
super.messageReceived(session, message);
public void channelRead0(ChannelHandlerContext ctx, String message) throws Exception {
//Log.e("KDE/LanLinkProvider", "Received a TCP packet from " + ctx.channel().remoteAddress() + ":" + message);
//Log.e("LanLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString());
//Log.e("LanLinkProvider","Received:"+message);
String theMessage = (String) message;
if (theMessage.isEmpty()) {
Log.w("KDE/LanLinkProvider","Empty package received");
if (message.isEmpty()) {
Log.e("KDE/LanLinkProvider", "Empty package received");
return;
}
NetworkPackage np = NetworkPackage.unserialize(theMessage);
final Channel channel = ctx.channel();
final NetworkPackage np = NetworkPackage.unserialize(message);
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
String myId = DeviceHelper.getDeviceId(context);
if (np.getString("deviceId").equals(myId)) {
Log.e("KDE/LanLinkProvider", "Somehow I'm connected to myself, ignoring. This should not happen.");
return;
}
//Log.i("KDE/LanLinkProvider", "Identity package received from " + np.getString("deviceName"));
Log.i("KDE/LanLinkProvider", "Identity package received from a stablished TCP connection from " + np.getString("deviceName"));
final LanLink.ConnectionStarted connectionStarted = LanLink.ConnectionStarted.Locally;
// Add ssl handler if device uses new protocol
try {
if (np.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
final SSLEngine sslEngine = SslHelper.getSslEngine(context, np.getString("deviceId"), SslHelper.SslMode.Client);
SslHandler sslHandler = new SslHandler(sslEngine);
channel.pipeline().addFirst(sslHandler);
sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<? super Channel>>() {
@Override
public void operationComplete(Future<? super Channel> future) throws Exception {
if (future.isSuccess()) {
Log.i("KDE/LanLinkProvider","Handshake successful with " + np.getString("deviceName") + " secured with " + sslEngine.getSession().getCipherSuite());
//Log.e("KDE/LanLinkProvider", "Channel" + channel.hashCode());
Certificate certificate = sslEngine.getSession().getPeerCertificates()[0];
np.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
addLink(np, channel, connectionStarted, true);
} else {
// Unpair if handshake failed
Log.e("KDE/LanLinkProvider", "Handshake as server failed with " + np.getString("deviceName"));
future.cause().printStackTrace();
if (future.cause() instanceof SSLHandshakeException) {
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(np.getString("deviceId"));
if (device == null) return;
device.unpair();
}
});
}
}
}
});
} else {
addLink(np, channel, connectionStarted, false);
}
} catch (Exception e) {
e.printStackTrace();
}
LanLink link = new LanLink(session, np.getString("deviceId"), LanLinkProvider.this, BaseLink.ConnectionStarted.Locally);
nioSessions.put(session.getId(),link);
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size());
addLink(np, link);
} else {
LanLink prevLink = nioSessions.get(session.getId());
if (prevLink == null) {
Log.e("KDE/LanLinkProvider","Expecting an identity package (A)");
LanLink link = nioLinks.get(channel.hashCode());
if (link== null) {
Log.e("KDE/LanLinkProvider","Expecting an identity package instead of " + np.getType());
//Log.e("KDE/LanLinkProvider", "Channel" + channel.hashCode());
} else {
prevLink.injectNetworkPackage(np);
link.injectNetworkPackage(np);
}
}
}
};
}
@ChannelHandler.Sharable
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private final IoHandler udpHandler = new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession udpSession, Object message) throws Exception {
super.messageReceived(udpSession, message);
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
protected void channelRead0(final ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
try {
//We should receive a string thanks to the TextLineCodecFactory filter
String theMessage = (String) message;
String theMessage = packet.content().toString(CharsetUtil.UTF_8);
final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage);
final String deviceId = identityPackage.getString("deviceId");
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
Log.e("KDE/LanLinkProvider", "Expecting an identity package (B)");
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity package");
return;
} else {
String myId = DeviceHelper.getDeviceId(context);
if (identityPackage.getString("deviceId").equals(myId)) {
if (deviceId.equals(myId)) {
Log.i("KDE/LanLinkProvider", "Ignoring my own broadcast");
return;
}
}
//Log.i("KDE/LanLinkProvider", "Identity package received, creating link");
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
try{
Bootstrap b = new Bootstrap();
b.group(clientGroup);
b.channel(NioSocketChannel.class);
b.handler(new TcpInitializer());
int tcpPort = identityPackage.getInt("tcpPort", port);
final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort);
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
final NioSocketConnector connector = new NioSocketConnector();
connector.setHandler(tcpHandler);
connector.getSessionConfig().setKeepAlive(true);
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX);
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory));
final Channel channel = channelFuture.channel();
int tcpPort = identityPackage.getInt("tcpPort", port);
final ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
future.addListener(new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture ioFuture) {
try {
future.removeListener(this);
final IoSession session = ioFuture.getSession();
Log.i("KDE/LanLinkProvider", "Connection successful: " + session.isConnected());
final LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this, BaseLink.ConnectionStarted.Remotely);
new Thread(new Runnable() {
@Override
public void run() {
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
link.sendPackage(np2,new Device.SendPackageStatusCallback() {
if (!future.isSuccess()) {
Log.e("KDE/LanLinkProvider", "Cannot connect to " + deviceId);
if (!reverseConnectionBlackList.contains(deviceId)) {
Log.w("KDE/LanLinkProvider","Blacklisting "+deviceId);
reverseConnectionBlackList.add(deviceId);
reverseConnectionTimer = new Timer();
reverseConnectionTimer.schedule(new TimerTask() {
@Override
protected void onSuccess() {
nioSessions.put(session.getId(), link);
nioConnectors.put(session.getId(), connector);
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size());
addLink(identityPackage, link);
public void run() {
reverseConnectionBlackList.remove(deviceId);
}
}, 5*1000);
@Override
protected void onFailure(Throwable e) {
Log.e("KDE/LanLinkProvider", "Connection failed: could not send identity package back");
}
});
// Try to cause a reverse connection
onNetworkChange();
}
}).start();
} catch (Exception e) { //If we don't catch it here, Mina will swallow it :/
e.printStackTrace();
Log.e("KDE/LanLinkProvider", "sessionClosed exception");
return;
}
//Log.e("KDE/LanLinkProvider", "Connection successful: " + channel.isActive());
// Add ssl handler if device supports new protocol
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
// add ssl handler with start tls true
SSLEngine sslEngine = SslHelper.getSslEngine(context, deviceId, SslHelper.SslMode.Server);
SslHandler sslHandler = new SslHandler(sslEngine, true);
channel.pipeline().addFirst(sslHandler);
}
final LanLink.ConnectionStarted connectionStarted = LanLink.ConnectionStarted.Remotely;
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
ChannelFuture future2 = channel.writeAndFlush(np2.serialize()).sync();
if (!future2.isSuccess()) {
Log.e("KDE/LanLinkProvider", "Connection failed: could not send identity package back");
return;
}
// If ssl handler is in channel, add link after handshake is completed
final SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
if (sslHandler != null) {
//Log.e("KDE/LanLinkProvider", "Initiating SSL handshake");
sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<? super Channel>>() {
@Override
public void operationComplete(Future<? super Channel> future) throws Exception {
if (future.isSuccess()) {
try {
Log.i("KDE/LanLinkProvider", "Handshake successfully completed with " + identityPackage.getString("deviceName") + ", session secured with " + sslHandler.engine().getSession().getCipherSuite());
Certificate certificate = sslHandler.engine().getSession().getPeerCertificates()[0];
identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
addLink(identityPackage, channel, connectionStarted, true);
} catch (Exception e){
Log.e("KDE/LanLinkProvider", "Exception in addLink");
e.printStackTrace();
}
} else {
// Unpair if handshake failed
// Any exception or handshake exception ?
Log.e("KDE/LanLinkProvider", "Handshake as client failed with " + identityPackage.getString("deviceName"));
future.cause().printStackTrace();
if (future.cause() instanceof SSLHandshakeException) {
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
if (device == null) return;
device.unpair();
}
});
}
}
}
});
} else {
Log.w("KDE/LanLinkProvider", "Not using SSL");
addLink(identityPackage, channel, connectionStarted, false);
}
}
}
});
});
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
Log.e("KDE/LanLinkProvider","Exception receiving udp package!!");
e.printStackTrace();
}
}
};
private void addLink(NetworkPackage identityPackage, LanLink link) {
}
public class TcpInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
ch.config().setAllowHalfClosure(false); // Not sure how it will work, but we certainly don't want half closure
ch.config().setKeepAlive(true);
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(tcpHandler);
}
}
private void addLink(NetworkPackage identityPackage, Channel channel, LanLink.ConnectionStarted connectionOrigin, boolean useSsl) {
String deviceId = identityPackage.getString("deviceId");
Log.i("KDE/LanLinkProvider","addLink to "+deviceId);
LanLink oldLink = visibleComputers.get(deviceId);
if (oldLink == link) {
Log.e("KDE/LanLinkProvider", "oldLink == link. This should not happen!");
return;
}
visibleComputers.put(deviceId, link);
connectionAccepted(identityPackage, link);
if (oldLink != null) {
Log.i("KDE/LanLinkProvider","Removing old connection to same device");
oldLink.closeSocket();
connectionLost(oldLink);
LanLink currentLink = visibleComputers.get(deviceId);
if (currentLink != null) {
//Update old link
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
final Channel oldChannel = currentLink.reset(channel, connectionOrigin, useSsl);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
nioLinks.remove(oldChannel.hashCode());
//Log.e("KDE/LanLinkProvider", "Forgetting about channel " + channel.hashCode());
}
}, 500); //Stop accepting messages from the old channel after 500ms
nioLinks.put(channel.hashCode(), currentLink);
//Log.e("KDE/LanLinkProvider", "Replacing channel. old: "+ oldChannel.hashCode() + " - new: "+ channel.hashCode());
} else {
//Let's create the link
LanLink link = new LanLink(context, deviceId, this, channel, connectionOrigin, useSsl);
nioLinks.put(channel.hashCode(), link);
visibleComputers.put(deviceId, link);
connectionAccepted(identityPackage, link);
}
}
@@ -254,64 +397,64 @@ public class LanLinkProvider extends BaseLinkProvider {
this.context = context;
//This handles the case when I'm the new device in the network and somebody answers my introduction package
tcpAcceptor = new NioSocketAcceptor();
tcpAcceptor.setHandler(tcpHandler);
tcpAcceptor.getSessionConfig().setKeepAlive(true);
tcpAcceptor.getSessionConfig().setReuseAddress(true);
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX);
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data
tcpAcceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory));
udpGroup = new NioEventLoopGroup();
try {
Bootstrap udpBootstrap = new Bootstrap();
udpBootstrap.group(udpGroup);
udpBootstrap.channel(NioDatagramChannel.class);
udpBootstrap.option(ChannelOption.SO_BROADCAST, true);
udpBootstrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(udpHandler);
}
});
udpBootstrap.bind(new InetSocketAddress(port)).sync();
}catch (Exception e){
Log.e("KDE/LanLinkProvider","Exception setting up UDP server");
e.printStackTrace();
}
udpAcceptor = new NioDatagramAcceptor();
udpAcceptor.getSessionConfig().setReuseAddress(true); //Share port if existing
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
//This one will have the default MaxLineLength of 1KB
udpAcceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
)
);
clientGroup = new NioEventLoopGroup();
// Due to certificate request from SSL server to client, the certificate request message from device with latest android version to device with
// old android version causes a FATAL ALERT message stating that incorrect certificate request
// Server is disabled on these devices and using a reverse connection strategy. This works well for connection of these devices with kde
// and newer android versions. Although devices with android version less than ICS cannot connect to other devices who also have android version less
// than ICS because server is disabled on both
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Log.w("KDE/LanLinkProvider","Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
return;
}
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap tcpBootstrap = new ServerBootstrap();
tcpBootstrap.group(bossGroup, workerGroup);
tcpBootstrap.channel(NioServerSocketChannel.class);
tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100);
tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO));
tcpBootstrap.option(ChannelOption.SO_REUSEADDR, true);
tcpBootstrap.childHandler(new TcpInitializer());
tcpBootstrap.bind(new InetSocketAddress(port)).sync();
}catch (Exception e) {
Log.e("KDE/LanLinkProvider","Exception setting up TCP server");
e.printStackTrace();
}
}
@Override
public void onStart() {
//This handles the case when I'm the existing device in the network and receive a "hello" UDP package
Log.i("KDE/LanLinkProvider", "onStart");
Set<SocketAddress> addresses = udpAcceptor.getLocalAddresses();
for (SocketAddress address : addresses) {
Log.i("KDE/LanLinkProvider", "UDP unbind old address");
udpAcceptor.unbind(address);
}
//Log.i("KDE/LanLinkProvider", "UDP Bind.");
udpAcceptor.setHandler(udpHandler);
try {
udpAcceptor.bind(new InetSocketAddress(port));
} catch(Exception e) {
Log.e("KDE/LanLinkProvider", "Error: Could not bind udp socket");
e.printStackTrace();
}
boolean success = false;
int tcpPort = port;
while(!success) {
try {
tcpAcceptor.bind(new InetSocketAddress(tcpPort));
success = true;
} catch(Exception e) {
tcpPort++;
}
}
Log.i("KDE/LanLinkProvider","Using tcpPort "+tcpPort);
//I'm on a new network, let's be polite and introduce myself
final int finalTcpPort = tcpPort;
new Thread(new Runnable() {
@Override
public void run() {
@@ -325,7 +468,7 @@ public class LanLinkProvider extends BaseLinkProvider {
iplist.add("255.255.255.255"); //Default: broadcast.
NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
identity.set("tcpPort", finalTcpPort);
identity.set("tcpPort", port);
DatagramSocket socket = null;
byte[] bytes = null;
try {
@@ -343,7 +486,7 @@ public class LanLinkProvider extends BaseLinkProvider {
for (String ipstr : iplist) {
try {
InetAddress client = InetAddress.getByName(ipstr);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, client, port);
java.net.DatagramPacket packet = new java.net.DatagramPacket(bytes, bytes.length, client, port);
socket.send(packet);
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+packet.getAddress());
} catch (Exception e) {
@@ -353,7 +496,9 @@ public class LanLinkProvider extends BaseLinkProvider {
}
}
socket.close();
if (socket != null) {
socket.close();
}
}
}).start();
@@ -361,26 +506,22 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override
public void onNetworkChange() {
//Log.e("KDE/LanLinkProvider","onNetworkChange");
//FilesHelper.LogOpenFileCount();
//Keep existing connections open while unbinding the socket
tcpAcceptor.setCloseOnDeactivation(false);
onStop();
tcpAcceptor.setCloseOnDeactivation(true);
//FilesHelper.LogOpenFileCount();
onStart();
//FilesHelper.LogOpenFileCount();
}
@Override
public void onStop() {
udpAcceptor.unbind();
tcpAcceptor.unbind();
//Log.i("KDE/LanLinkProvider", "onStop");
try {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
udpGroup.shutdownGracefully();
clientGroup.shutdownGracefully();
}catch (Exception e){
e.printStackTrace();
}
}
@Override

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect_tp.R;
import java.security.KeyFactory;
import java.security.cert.CertificateEncodingException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Timer;
import java.util.TimerTask;
public class LanPairingHandler extends BasePairingHandler {
public LanPairingHandler(Device device, final PairingHandlerCallback callback) {
super(device, callback);
if (device.isPaired()) {
mPairStatus = PairStatus.Paired;
} else {
mPairStatus = PairStatus.NotPaired;
}
}
@Override
public NetworkPackage createPairPackage() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", true);
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(mDevice.getContext());
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
np.set("publicKey", publicKey);
return np;
}
@Override
public void packageReceived(NetworkPackage np) throws Exception{
boolean wantsPair = np.getBoolean("pair");
if (wantsPair == isPaired()) {
if (mPairStatus == PairStatus.Requested) {
//Log.e("Device","Unpairing (pair rejected)");
mPairStatus = PairStatus.NotPaired;
hidePairingNotification();
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
}
return;
}
if (wantsPair) {
//Retrieve their public key
try {
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", "");
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
mDevice.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
} catch (Exception e) {
//IGNORE
}
if (mPairStatus == PairStatus.Requested) { //We started pairing
hidePairingNotification();
pairingDone();
} else {
// If device is already paired, accept pairing silently
if (mDevice.isPaired()) {
acceptPairing();
return;
}
// Pairing notifications are still managed by device as there is no other way to
// know about notificationId to cancel notification when PairActivity is started
// Even putting notificationId in intent does not work because PairActivity can be
// started from MainActivity too, so then notificationId cannot be set
hidePairingNotification();
mDevice.displayPairingNotification();
mPairingTimer = new Timer();
mPairingTimer.schedule(new TimerTask() {
@Override
public void run() {
Log.w("KDE/Device","Unpairing (timeout B)");
mPairStatus = PairStatus.NotPaired;
hidePairingNotification();
}
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
mPairStatus = PairStatus.RequestedByPeer;
mCallback.incomingRequest();
}
} else {
Log.i("KDE/Pairing", "Unpair request");
if (mPairStatus == PairStatus.Requested) {
hidePairingNotification();
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
} else if (mPairStatus == PairStatus.Paired) {
mCallback.unpaired();
}
mPairStatus = PairStatus.NotPaired;
}
}
@Override
public void requestPairing() {
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
@Override
protected void onSuccess() {
hidePairingNotification(); //Will stop the pairingTimer if it was running
mPairingTimer = new Timer();
mPairingTimer.schedule(new TimerTask() {
@Override
public void run() {
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
Log.w("KDE/Device","Unpairing (timeout A)");
mPairStatus = PairStatus.NotPaired;
}
}, 30*1000); //Time to wait for the other to accept
mPairStatus = PairStatus.Requested;
}
@Override
protected void onFailure(Throwable e) {
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package));
}
};
mDevice.sendPackage(createPairPackage(), statusCallback);
}
public void hidePairingNotification() {
mDevice.hidePairingNotification();
if (mPairingTimer != null) {
mPairingTimer .cancel();
}
}
@Override
public void acceptPairing() {
hidePairingNotification();
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
@Override
protected void onSuccess() {
pairingDone();
}
@Override
protected void onFailure(Throwable e) {
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
}
};
mDevice.sendPackage(createPairPackage(), statusCallback);
}
@Override
public void rejectPairing() {
hidePairingNotification();
mPairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", false);
mDevice.sendPackage(np);
}
@Override
public void pairingDone() {
// Store device information needed to create a Device object in a future
//Log.e("KDE/PairingDone", "Pairing Done");
SharedPreferences.Editor editor = mDevice.getContext().getSharedPreferences(mDevice.getDeviceId(), Context.MODE_PRIVATE).edit();
try {
String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey);
} catch (Exception e) {
Log.e("KDE/PairingDone", "Error encoding public key");
}
try {
String encodedCertificate = Base64.encodeToString(mDevice.certificate.getEncoded(), 0);
editor.putString("certificate", encodedCertificate);
} catch (NullPointerException n) {
Log.w("KDE/PairingDone", "Certificate is null, remote device does not support ssl");
} catch (CertificateEncodingException c) {
Log.e("KDE/PairingDOne", "Error encoding certificate");
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Pairng", "Exception");
}
editor.apply();
mPairStatus = PairStatus.Paired;
mCallback.pairingDone();
}
@Override
public void unpair() {
mPairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", false);
mDevice.sendPackage(np);
}
}

View File

@@ -20,44 +20,47 @@
package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.content.Context;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.NetworkPackage;
import java.security.PublicKey;
import java.util.ArrayList;
public class LoopbackLink extends BaseLink {
public LoopbackLink(BaseLinkProvider linkProvider) {
super("loopback", linkProvider, ConnectionStarted.Remotely);
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
super(context, "loopback", linkProvider);
}
@Override
public String getName() {
return "LoopbackLink";
}
@Override
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
return new LoopbackPairingHandler(device, callback);
}
@Override
public void sendPackage(NetworkPackage in, Device.SendPackageStatusCallback callback) {
sendPackageEncrypted(in, callback, null);
packageReceived(in);
if (in.hasPayload()) {
callback.sendProgress(0);
in.setPayload(in.getPayload(), in.getPayloadSize());
callback.sendProgress(100);
}
callback.sendSuccess();
}
@Override
public void sendPackageEncrypted(NetworkPackage in, Device.SendPackageStatusCallback callback, PublicKey key) {
try {
if (key != null) {
in = in.encrypt(key);
}
String s = in.serialize();
NetworkPackage out= NetworkPackage.unserialize(s);
if (key != null) {
out = out.decrypt(privateKey);
}
packageReceived(out);
if (in.hasPayload()) {
callback.sendProgress(0);
out.setPayload(in.getPayload(), in.getPayloadSize());
callback.sendProgress(100);
}
callback.sendSuccess();
} catch(Exception e) {
callback.sendFailure(e);
}
public void sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
sendPackage(np, callback);
}
}

View File

@@ -45,7 +45,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
@Override
public void onNetworkChange() {
NetworkPackage np = NetworkPackage.createIdentityPackage(context);
connectionAccepted(np, new LoopbackLink(this));
connectionAccepted(np, new LoopbackLink(context, this));
}
/*
@Override

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends.LoopbackBackend;
import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler;
import org.kde.kdeconnect.Device;
public class LoopbackPairingHandler extends LanPairingHandler{
public LoopbackPairingHandler(Device device, PairingHandlerCallback callback) {
super(device, callback);
}
// Extending from LanPairingHandler, as it is similar to it
}

View File

@@ -29,18 +29,15 @@ import android.content.SharedPreferences;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -156,13 +153,17 @@ public class BackgroundService extends Service {
}
public ArrayList<BaseLinkProvider> getLinkProviders() {
return linkProviders;
}
public Device getDevice(String id) {
return devices.get(id);
}
private void cleanDevices() {
for(Device d : devices.values()) {
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByOtherEnd() && d.getConnectionSource() == BaseLink.ConnectionStarted.Remotely) {
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
d.disconnect();
}
}
@@ -182,8 +183,8 @@ public class BackgroundService extends Service {
} else {
Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId);
device = new Device(BackgroundService.this, identityPackage, link);
if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByOtherEnd()
|| link.getConnectionSource() == BaseLink.ConnectionStarted.Locally
if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer()
|| link.linkShouldBeKeptAlive()
||!discoveryModeAcquisitions.isEmpty() )
{
devices.put(deviceId, device);
@@ -254,7 +255,7 @@ public class BackgroundService extends Service {
Log.i("KDE/BackgroundService", "Service not started yet, initializing...");
initializeRsaKeys();
initializeSecurityParameters();
loadRememberedDevicesFromSettings();
registerLinkProviders();
@@ -264,62 +265,11 @@ public class BackgroundService extends Service {
for (BaseLinkProvider a : linkProviders) {
a.onStart();
}
}
private void initializeRsaKeys() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (!settings.contains("publicKey") || !settings.contains("privateKey")) {
KeyPair keyPair;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keyPair = keyGen.genKeyPair();
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/initializeRsaKeys","Exception");
return;
}
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
SharedPreferences.Editor edit = settings.edit();
edit.putString("publicKey",Base64.encodeToString(publicKey, 0).trim()+"\n");
edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
edit.apply();
}
/*
// Encryption and decryption test
//================================
try {
NetworkPackage np = NetworkPackage.createIdentityPackage(this);
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(this);
byte[] publicKeyBytes = Base64.decode(globalSettings.getString("publicKey",""), 0);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
np.encrypt(publicKey);
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
NetworkPackage decrypted = np.decrypt(privateKey);
Log.e("ENCRYPTION AND DECRYPTION TEST", decrypted.serialize());
} catch (Exception e) {
e.printStackTrace();
Log.e("ENCRYPTION AND DECRYPTION TEST","Exception: "+e);
}
*/
void initializeSecurityParameters() {
RsaHelper.initialiseRsaKeys(this);
SslHelper.initialiseCertificate(this);
}
@Override
@@ -350,11 +300,14 @@ public class BackgroundService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
//This will be called for each intent launch, even if the service is already started and it is reused
mutex.lock();
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
try {
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
}
callbacks.clear();
} finally {
mutex.unlock();
}
callbacks.clear();
mutex.unlock();
return Service.START_STICKY;
}
@@ -368,8 +321,11 @@ public class BackgroundService extends Service {
public void run() {
if (callback != null) {
mutex.lock();
callbacks.add(callback);
mutex.unlock();
try {
callbacks.add(callback);
} finally {
mutex.unlock();
}
}
Intent serviceIntent = new Intent(c, BackgroundService.class);
c.startService(serviceIntent);

View File

@@ -28,31 +28,38 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Base64;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Helpers.ObjectsHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect_tp.R;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -63,21 +70,37 @@ public class Device implements BaseLink.PackageReceiver {
private final String deviceId;
private String name;
public PublicKey publicKey;
public Certificate certificate;
private int notificationId;
private int protocolVersion;
private DeviceType deviceType;
private PairStatus pairStatus;
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
private Timer pairingTimer;
private Map<String, BasePairingHandler> pairingHandlers = new HashMap<String, BasePairingHandler>();
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
private ArrayList<String> incomingCapabilities = new ArrayList<>();
private ArrayList<String> outgoingCapabilities = new ArrayList<>();
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> failedPlugins = new ConcurrentHashMap<>();
private ArrayList<String> unsupportedPlugins = new ArrayList<>();
private HashSet<String> supportedIncomingInterfaces = new HashSet<>();
private HashSet<String> supportedOutgoingInterfaces = new HashSet<>();
private HashMap<String, ArrayList<String>> pluginsByIncomingInterface;
private HashMap<String, ArrayList<String>> pluginsByOutgoingInterface;
private final SharedPreferences settings;
public ArrayList<String> getUnsupportedPlugins() {
return unsupportedPlugins;
}
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
public interface PluginsChangedListener {
@@ -86,8 +109,6 @@ public class Device implements BaseLink.PackageReceiver {
public enum PairStatus {
NotPaired,
Requested,
RequestedByPeer,
Paired
}
@@ -131,12 +152,14 @@ public class Device implements BaseLink.PackageReceiver {
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
try {
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
String publicKeyStr = settings.getString("publicKey", null);
if (publicKeyStr != null) {
byte[] publicKeyBytes = Base64.decode(publicKeyStr, 0);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
}
} catch (Exception e) {
e.printStackTrace();
unpair();
Log.e("KDE/Device","Exception");
Log.e("KDE/Device","Exception deserializing stored public key for device");
}
reloadPluginsFromSettings();
@@ -183,6 +206,10 @@ public class Device implements BaseLink.PackageReceiver {
return deviceId;
}
public Context getContext() {
return context;
}
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() {
return protocolVersion - NetworkPackage.ProtocolVersion;
@@ -191,7 +218,6 @@ public class Device implements BaseLink.PackageReceiver {
//
// Pairing-related functions
//
@@ -200,17 +226,28 @@ public class Device implements BaseLink.PackageReceiver {
return pairStatus == PairStatus.Paired;
}
/* Asks all pairing handlers that, is pair requested? */
public boolean isPairRequested() {
return pairStatus == PairStatus.Requested;
boolean pairRequested = false;
for (BasePairingHandler ph: pairingHandlers.values()) {
pairRequested = pairRequested || ph.isPairRequested();
}
return pairRequested;
}
public boolean isPairRequestedByOtherEnd() {
return pairStatus == PairStatus.RequestedByPeer;
/* Asks all pairing handlers that, is pair requested by peer? */
public boolean isPairRequestedByPeer() {
boolean pairRequestedByPeer = false;
for (BasePairingHandler ph : pairingHandlers.values()) {
pairRequestedByPeer = pairRequestedByPeer || ph.isPairRequestedByPeer();
}
return pairRequestedByPeer;
}
public void addPairingCallback(PairingCallback callback) {
pairingCallback.add(callback);
}
public void removePairingCallback(PairingCallback callback) {
pairingCallback.remove(callback);
}
@@ -225,15 +262,6 @@ public class Device implements BaseLink.PackageReceiver {
cb.pairingFailed(res.getString(R.string.error_already_paired));
}
return;
case Requested:
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_requested));
}
return;
case RequestedByPeer:
Log.d("requestPairing", "Pairing already started by the other end, accepting their request.");
acceptPairing();
return;
case NotPaired:
;
}
@@ -245,59 +273,35 @@ public class Device implements BaseLink.PackageReceiver {
return;
}
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageStatusCallback() {
@Override
public void onSuccess() {
hidePairingNotification(); //Will stop the pairingTimer if it was running
pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() {
@Override
public void run() {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_timed_out));
}
Log.e("KDE/Device", "Unpairing (timeout A)");
pairStatus = PairStatus.NotPaired;
}
}, 30 * 1000); //Time to wait for the other to accept
pairStatus = PairStatus.Requested;
}
@Override
public void onFailure(Throwable e) {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
}
Log.e("KDE/Device", "Unpairing (sendFailed A)");
pairStatus = PairStatus.NotPaired;
}
});
}
public void hidePairingNotification() {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
if (pairingTimer != null) {
pairingTimer.cancel();
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.requestPairing();
}
BackgroundService.removeGuiInUseCounter(context);
}
public void unpair() {
//Log.e("Device","Unpairing (unpair)");
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.unpair();
}
unpairInternal(); // Even if there are no pairing handlers, unpair
}
/**
* This method does not send an unpair package, instead it unpairs internally by deleting trusted device info. . Likely to be called after sending package from
* pairing handler
*/
private void unpairInternal() {
//Log.e("Device","Unpairing (unpairInternal)");
pairStatus = PairStatus.NotPaired;
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).apply();
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", false);
sendPackage(np);
// FIXME : We delete all device info here, but the xml file still persists
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
devicePreferences.edit().clear().apply();
for (PairingCallback cb : pairingCallback) cb.unpaired();
@@ -305,6 +309,7 @@ public class Device implements BaseLink.PackageReceiver {
}
/* This method should be called after pairing is done from pairing handler. Calling this method again should not create any problem as most of the things will get over writter*/
private void pairingDone() {
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
@@ -317,12 +322,9 @@ public class Device implements BaseLink.PackageReceiver {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).apply();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName());
SharedPreferences.Editor editor = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE).edit();
editor.putString("deviceName", name);
editor.putString("deviceType", deviceType.toString());
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey);
editor.apply();
reloadPluginsFromSettings();
@@ -333,39 +335,28 @@ public class Device implements BaseLink.PackageReceiver {
}
/* This method is called after accepting pair request form GUI */
public void acceptPairing() {
Log.i("KDE/Device","Accepted pair request started by the other device");
Log.i("KDE/Device", "Accepted pair request started by the other device");
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageStatusCallback() {
@Override
protected void onSuccess() {
pairingDone();
}
@Override
protected void onFailure(Throwable e) {
Log.e("Device","Unpairing (sendFailed B)");
pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_not_reachable));
}
}
});
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.acceptPairing();
}
}
/* This method is called after rejecting pairing from GUI */
public void rejectPairing() {
Log.i("KDE/Device","Rejected pair request started by the other device");
Log.i("KDE/Device", "Rejected pair request started by the other device");
//Log.e("Device","Unpairing (rejectPairing)");
pairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", false);
sendPackage(np);
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.rejectPairing();
}
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
@@ -373,8 +364,52 @@ public class Device implements BaseLink.PackageReceiver {
}
//
// Notification related methods used during pairing
//
public int getNotificationId() {
return notificationId;
}
public void displayPairingNotification() {
hidePairingNotification();
notificationId = (int)System.currentTimeMillis();
Intent intent = new Intent(getContext(), MaterialActivity.class);
intent.putExtra("deviceId", getDeviceId());
intent.putExtra("notificationId", notificationId);
PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT);
Resources res = getContext().getResources();
Notification noti = new NotificationCompat.Builder(getContext())
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
.setContentText(res.getString(R.string.tap_to_answer))
.setContentIntent(pendingIntent)
.setTicker(res.getString(R.string.pair_requested))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
try {
BackgroundService.addGuiInUseCounter(context);
notificationManager.notify(notificationId, noti);
} catch(Exception e) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
}
public void hidePairingNotification() {
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
BackgroundService.removeGuiInUseCounter(context);
}
//
// ComputerLink-related functions
@@ -400,6 +435,20 @@ public class Device implements BaseLink.PackageReceiver {
this.deviceType = DeviceType.FromString(identityPackage.getString("deviceType", "desktop"));
}
if (identityPackage.has("certificate")) {
String certificateString = identityPackage.getString("certificate");
try {
byte[] certificateBytes = Base64.decode(certificateString, 0);
certificate = SslHelper.parseCertificate(certificateBytes);
Log.i("KDE/Device", "Got certificate ");
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Error getting certificate");
}
}
links.add(link);
@@ -415,6 +464,36 @@ public class Device implements BaseLink.PackageReceiver {
Log.i("KDE/Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size());
if (!pairingHandlers.containsKey(link.getName())) {
BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() {
@Override
public void incomingRequest() {
for (PairingCallback cb : pairingCallback) {
cb.incomingRequest();
}
}
@Override
public void pairingDone() {
Device.this.pairingDone();
}
@Override
public void pairingFailed(String error) {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(error);
}
}
@Override
public void unpaired() {
unpairInternal();
}
};
pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback));
}
pairingHandlers.get(link.getName()).setLink(link);
/*
Collections.sort(links, new Comparator<BaseLink>() {
@Override
@@ -434,9 +513,21 @@ public class Device implements BaseLink.PackageReceiver {
public void removeLink(BaseLink link) {
//FilesHelper.LogOpenFileCount();
/* Remove pairing handler corresponding to that link too if it was the only link*/
boolean linkPresent = false;
for (BaseLink bl : links) {
if (bl.getName().equals(link.getName())) {
linkPresent = true;
break;
}
}
if (!linkPresent) {
pairingHandlers.remove(link.getName());
}
link.removePackageReceiver(this);
links.remove(link);
Log.i("KDE/Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size());
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (links.isEmpty()) {
reloadPluginsFromSettings();
}
@@ -445,139 +536,72 @@ public class Device implements BaseLink.PackageReceiver {
@Override
public void onPackageReceived(NetworkPackage np) {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) {
hackToMakeRetrocompatiblePacketTypes(np);
Log.i("KDE/Device","Pair package");
if (NetworkPackage.PACKAGE_TYPE_PAIR.equals(np.getType())) {
boolean wantsPair = np.getBoolean("pair");
Log.i("KDE/Device", "Pair package");
if (wantsPair == isPaired()) {
if (pairStatus == PairStatus.Requested) {
//Log.e("Device","Unpairing (pair rejected)");
pairStatus = PairStatus.NotPaired;
hidePairingNotification();
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
}
return;
}
if (wantsPair) {
//Retrieve their public key
for (BasePairingHandler ph: pairingHandlers.values()) {
try {
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n","");
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/Device","Pairing exception: Received incorrect key");
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_invalid_key));
}
return;
}
if (pairStatus == PairStatus.Requested) { //We started pairing
hidePairingNotification();
pairingDone();
} else {
Intent intent = new Intent(context, MaterialActivity.class);
intent.putExtra("deviceId", deviceId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
Resources res = context.getResources();
hidePairingNotification();
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
.setContentText(res.getString(R.string.tap_to_answer))
.setContentIntent(pendingIntent)
.setTicker(res.getString(R.string.pair_requested))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationId = (int)System.currentTimeMillis();
try {
BackgroundService.addGuiInUseCounter(context);
notificationManager.notify(notificationId, noti);
} catch(Exception e) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() {
@Override
public void run() {
Log.e("KDE/Device","Unpairing (timeout B)");
hidePairingNotification();
pairStatus = PairStatus.NotPaired;
}
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
pairStatus = PairStatus.RequestedByPeer;
for (PairingCallback cb : pairingCallback) cb.incomingRequest();
}
} else {
Log.i("KDE/Pairing","Unpair request");
if (pairStatus == PairStatus.Requested) {
hidePairingNotification();
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
} else if (pairStatus == PairStatus.Paired) {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).apply();
}
pairStatus = PairStatus.NotPaired;
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.unpaired();
}
} else if (isPaired()) {
for (Plugin plugin : plugins.values()) {
try {
plugin.onPackageReceived(np);
ph.packageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived()");
Log.e("PairingPackageReceived","Exception");
}
}
} else if (NetworkPackage.PACKAGE_TYPE_CAPABILITIES.equals(np.getType())) {
ArrayList<String> newIncomingCapabilities = np.getStringList("IncomingCapabilities");
ArrayList<String> newOutgoingCapabilities = np.getStringList("OutgoingCapabilities");
if (!ObjectsHelper.equals(newIncomingCapabilities, incomingCapabilities) ||
!ObjectsHelper.equals(newOutgoingCapabilities, outgoingCapabilities)) {
incomingCapabilities = newIncomingCapabilities;
outgoingCapabilities = newOutgoingCapabilities;
reloadPluginsFromSettings();
}
} else if (isPaired()) {
//If capabilities are not supported, iterate all plugins
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
if (targetPlugins != null && !targetPlugins.isEmpty()) {
for (String pluginKey : targetPlugins) {
Plugin plugin = plugins.get(pluginKey);
try {
plugin.onPackageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPackageReceived()");
}
}
} else {
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
}
} else {
//Log.e("KDE/onPackageReceived","Device not paired, will pass package to unpairedPackageListeners");
if (pairStatus != PairStatus.Requested) {
unpair();
}
// If it is pair package, it should be captured by "if" at start
// If not and device is paired, it should be captured by isPaired
// Else unpair, this handles the situation when one device unpairs, but other dont know like unpairing when wi-fi is off
for (Plugin plugin : plugins.values()) {
try {
plugin.onUnpairedDevicePackageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived() in unPairedPackageListeners");
unpair();
//If capabilities are not supported, iterate all plugins
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
if (targetPlugins != null && !targetPlugins.isEmpty()) {
for (String pluginKey : targetPlugins) {
Plugin plugin = plugins.get(pluginKey);
try {
plugin.onUnpairedDevicePackageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in " + plugin.getDisplayName() + "'s onPackageReceived() in unPairedPackageListeners");
}
}
} else {
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
}
}
}
@@ -617,6 +641,13 @@ public class Device implements BaseLink.PackageReceiver {
//Async
public void sendPackage(final NetworkPackage np, final SendPackageStatusCallback callback) {
hackToMakeRetrocompatiblePacketTypes(np);
if (protocolVersion >= 6 && !supportedOutgoingInterfaces.contains(np.getType())) {
Log.e("Device/sendPackage", "Plugin tried to send an unsupported package: " + np.getType());
Log.e("Device/sendPackage", "Supported package types: " + Arrays.toString(supportedOutgoingInterfaces.toArray()));
}
//Log.e("sendPackage", "Sending package...");
//Log.e("sendPackage", np.serialize());
@@ -625,7 +656,7 @@ public class Device implements BaseLink.PackageReceiver {
@Override
public void run() {
boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
boolean useEncryption = (protocolVersion < 6 && (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()));
//Make a copy to avoid concurrent modification exception if the original list changes
for (final BaseLink link : links) {
@@ -672,46 +703,40 @@ public class Device implements BaseLink.PackageReceiver {
return plugin;
}
private synchronized void addPlugin(final String pluginKey) {
private synchronized boolean addPlugin(final String pluginKey) {
Plugin existing = plugins.get(pluginKey);
if (existing != null) {
Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
return;
//Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
return true;
}
final Plugin plugin = PluginFactory.instantiatePluginForDevice(context, pluginKey, this);
if (plugin == null) {
Log.e("KDE/addPlugin","could not instantiate plugin: "+pluginKey);
failedPlugins.put(pluginKey, null);
return;
return false;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
boolean success;
try {
success = plugin.onCreate();
} catch (Exception e) {
success = false;
e.printStackTrace();
Log.e("KDE/addPlugin", "Exception loading plugin " + pluginKey);
}
boolean success;
try {
success = plugin.onCreate();
} catch (Exception e) {
success = false;
e.printStackTrace();
Log.e("KDE/addPlugin", "Exception loading plugin " + pluginKey);
}
if (success) {
//Log.e("addPlugin","added " + pluginKey);
failedPlugins.remove(pluginKey);
plugins.put(pluginKey, plugin);
} else {
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
plugins.remove(pluginKey);
failedPlugins.put(pluginKey, plugin);
}
}
});
if (success) {
//Log.e("addPlugin","added " + pluginKey);
failedPlugins.remove(pluginKey);
plugins.put(pluginKey, plugin);
} else {
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
plugins.remove(pluginKey);
failedPlugins.put(pluginKey, plugin);
}
return success;
}
private synchronized boolean removePlugin(String pluginKey) {
@@ -735,22 +760,12 @@ public class Device implements BaseLink.PackageReceiver {
Log.e("KDE/removePlugin","Exception calling onDestroy for plugin "+pluginKey);
}
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(this);
}
return true;
}
public void setPluginEnabled(String pluginKey, boolean value) {
settings.edit().putBoolean(pluginKey,value).apply();
if (value && isPaired() && isReachable()) addPlugin(pluginKey);
else removePlugin(pluginKey);
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(Device.this);
}
reloadPluginsFromSettings();
}
public boolean isPluginEnabled(String pluginKey) {
@@ -769,20 +784,102 @@ public class Device implements BaseLink.PackageReceiver {
Set<String> availablePlugins = PluginFactory.getAvailablePlugins();
for(String pluginKey : availablePlugins) {
boolean enabled = false;
boolean listenToUnpaired = PluginFactory.getPluginInfo(context, pluginKey).listenToUnpaired();
ArrayList<String> newUnsupportedPlugins = new ArrayList<>();
HashSet<String> newSupportedIncomingInterfaces = new HashSet<>();
HashSet<String> newSupportedOutgoingInterfaces = new HashSet<>();
HashMap<String, ArrayList<String>> newPluginsByIncomingInterface = new HashMap<>();
HashMap<String, ArrayList<String>> newPluginsByOutgoingInterface = new HashMap<>();
final boolean supportsCapabilities = (protocolVersion >= 6);
for (String pluginKey : availablePlugins) {
PluginFactory.PluginInfo pluginInfo = PluginFactory.getPluginInfo(context, pluginKey);
Set<String> incomingInterfaces = pluginInfo.getSupportedPackageTypes();
Set<String> outgoingInterfaces = pluginInfo.getOutgoingPackageTypes();
boolean pluginEnabled = false;
boolean listenToUnpaired = pluginInfo.listenToUnpaired();
if ((isPaired() || listenToUnpaired) && isReachable()) {
enabled = isPluginEnabled(pluginKey);
pluginEnabled = isPluginEnabled(pluginKey);
}
if (enabled) {
addPlugin(pluginKey);
//TODO: Check for plugins that will fail to load before checking the capabilities
if (supportsCapabilities && (!incomingInterfaces.isEmpty() || !outgoingInterfaces.isEmpty())) {
HashSet<String> supportedOut = new HashSet<>(outgoingInterfaces);
supportedOut.retainAll(incomingCapabilities); //Intersection
HashSet<String> supportedIn = new HashSet<>(incomingInterfaces);
supportedIn.retainAll(outgoingCapabilities);
if (supportedOut.isEmpty() && supportedIn.isEmpty()) {
newUnsupportedPlugins.add(pluginKey);
if (pluginEnabled) {
//We still want to announce this capability, to prevent a deadlock
newSupportedOutgoingInterfaces.addAll(outgoingInterfaces);
newSupportedIncomingInterfaces.addAll(incomingInterfaces);
pluginEnabled = false;
}
}
}
if (pluginEnabled) {
boolean success = addPlugin(pluginKey);
if (success) {
newSupportedIncomingInterfaces.addAll(incomingInterfaces);
newSupportedOutgoingInterfaces.addAll(outgoingInterfaces);
for (String packageType : incomingInterfaces) {
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
ArrayList<String> plugins = newPluginsByIncomingInterface.get(packageType);
if (plugins == null) plugins = new ArrayList<>();
plugins.add(pluginKey);
newPluginsByIncomingInterface.put(packageType, plugins);
}
for (String packageType : outgoingInterfaces) {
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
ArrayList<String> plugins = newPluginsByOutgoingInterface.get(packageType);
if (plugins == null) plugins = new ArrayList<>();
plugins.add(pluginKey);
newPluginsByOutgoingInterface.put(packageType, plugins);
}
}
} else {
removePlugin(pluginKey);
}
}
boolean capabilitiesChanged = false;
if (!newSupportedIncomingInterfaces.equals(supportedIncomingInterfaces) ||
!newSupportedOutgoingInterfaces.equals(pluginsByOutgoingInterface)) {
capabilitiesChanged = true;
}
pluginsByOutgoingInterface = newPluginsByOutgoingInterface;
pluginsByIncomingInterface = newPluginsByIncomingInterface;
supportedIncomingInterfaces = newSupportedIncomingInterfaces;
supportedOutgoingInterfaces = newSupportedOutgoingInterfaces;
unsupportedPlugins = newUnsupportedPlugins;
Log.i("ReloadPlugins", "not loading " + Arrays.toString(unsupportedPlugins.toArray()) + " because of unmatched capabilities");
onPluginsChanged();
//Only send capabilities to devices using protocol version 6 or later
if (capabilitiesChanged && isReachable() && isPaired() && protocolVersion >= 6) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CAPABILITIES);
np.set("IncomingCapabilities", new ArrayList<>(newSupportedIncomingInterfaces));
np.set("OutgoingCapabilities", new ArrayList<>(newSupportedOutgoingInterfaces));
sendPackage(np);
}
}
public void onPluginsChanged() {
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(Device.this);
}
@@ -810,13 +907,23 @@ public class Device implements BaseLink.PackageReceiver {
}
}
public BaseLink.ConnectionStarted getConnectionSource() {
public boolean deviceShouldBeKeptAlive() {
for(BaseLink l : links) {
if (l.getConnectionSource() == BaseLink.ConnectionStarted.Locally) {
return BaseLink.ConnectionStarted.Locally;
if (l.linkShouldBeKeptAlive()) {
return true;
}
}
return BaseLink.ConnectionStarted.Remotely;
return false;
}
public void hackToMakeRetrocompatiblePacketTypes(NetworkPackage np) {
if (protocolVersion >= 6) return;
np.mType = np.getType().replace(".request","");
}
public String hackToMakeRetrocompatiblePacketTypes(String type) {
if (protocolVersion >= 6) return type;
return type.replace(".request","");
}
}

View File

@@ -21,46 +21,87 @@
package org.kde.kdeconnect.Helpers;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.PhoneLookup;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class ContactsHelper {
public static String phoneNumberLookup(Context context, String number) {
public static Map<String, String> phoneNumberLookup(Context context, String number) {
//Log.e("PhoneNumberLookup", number);
Map<String, String> contactInfo = new HashMap<String, String>();
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(
uri,
new String[] {
PhoneLookup.DISPLAY_NAME
PhoneLookup.DISPLAY_NAME,
ContactsContract.PhoneLookup.PHOTO_URI
/*, PhoneLookup.TYPE
, PhoneLookup.LABEL
, PhoneLookup.ID */
},
null, null, null);
} catch (IllegalArgumentException e) {
return number;
return contactInfo;
}
// Take the first match only
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
if (nameIndex != -1) {
String name = cursor.getString(nameIndex);
//Log.e("PhoneNumberLookup", "success: " + name);
contactInfo.put("name", cursor.getString(nameIndex));
}
nameIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
if (nameIndex != -1) {
contactInfo.put("photoID", cursor.getString(nameIndex));
}
if (!contactInfo.isEmpty()) {
cursor.close();
return name + " (" + number + ")";
return contactInfo;
}
}
if (cursor != null) cursor.close();
return contactInfo;
}
return number;
public static String photoId64Encoded(Context context, String photoId) {
Uri photoUri = Uri.parse(photoId);
Uri displayPhotoUri = Uri.withAppendedPath(photoUri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
byte[] buffer = null;
Base64OutputStream out = null;
ByteArrayOutputStream encodedPhoto = null;
try {
encodedPhoto = new ByteArrayOutputStream();
out = new Base64OutputStream(encodedPhoto, Base64.DEFAULT);
InputStream fd2 = context.getContentResolver().openInputStream(photoUri);
buffer = new byte[1024];
int len;
while ((len = fd2.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return encodedPhoto.toString();
} catch (Exception ex) {
Log.e("ContactsHelper", ex.toString());
return new String();
}
}
}

View File

@@ -0,0 +1,7 @@
package org.kde.kdeconnect.Helpers;
public class ObjectsHelper {
public static boolean equals(Object a, Object b) {
return (a == null) ? (b == null) : a.equals(b);
}
}

View File

@@ -0,0 +1,24 @@
package org.kde.kdeconnect.Helpers;
import android.util.Log;
import java.security.SecureRandom;
public class RandomHelper {
public static SecureRandom secureRandom = new SecureRandom();
private static final char[] symbols = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
"abcdefghijklmnopqrstuvwxyz"+
"1234567890").toCharArray();
public static String randomString(int length) {
char[] buffer= new char[length];
for (int idx = 0; idx < length; ++idx) {
buffer[idx] = symbols[secureRandom.nextInt(symbols.length)];
}
return new String(buffer);
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Helpers.SecurityHelpers;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.kde.kdeconnect.NetworkPackage;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
public class RsaHelper {
public static void initialiseRsaKeys(Context context) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
if (!settings.contains("publicKey") || !settings.contains("privateKey")) {
KeyPair keyPair;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keyPair = keyGen.genKeyPair();
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/initializeRsaKeys", "Exception");
return;
}
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
SharedPreferences.Editor edit = settings.edit();
edit.putString("publicKey", Base64.encodeToString(publicKey, 0).trim()+"\n");
edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
edit.apply();
}
}
public static PublicKey getPublicKey (Context context) throws Exception{
try {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
return publicKey;
}catch (Exception e){
throw e;
}
}
public static PublicKey getPublicKey(Context context, String deviceId) throws Exception{
try {
SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
return publicKey;
} catch (Exception e) {
throw e;
}
}
public static PrivateKey getPrivateKey(Context context) throws Exception{
try {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
return privateKey;
} catch (Exception e) {
throw e;
}
}
public static NetworkPackage encrypt(NetworkPackage np, PublicKey publicKey) throws GeneralSecurityException, JSONException {
String serialized = np.serialize();
int chunkSize = 128;
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
JSONArray chunks = new JSONArray();
while (serialized.length() > 0) {
if (serialized.length() < chunkSize) {
chunkSize = serialized.length();
}
String chunk = serialized.substring(0, chunkSize);
serialized = serialized.substring(chunkSize);
byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
byte[] encryptedChunk;
encryptedChunk = cipher.doFinal(chunkBytes);
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
}
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED);
encrypted.set("data", chunks);
encrypted.setPayload(np.getPayload(), np.getPayloadSize());
return encrypted;
}
public static NetworkPackage decrypt(NetworkPackage np, PrivateKey privateKey) throws GeneralSecurityException, JSONException {
JSONArray chunks = np.getJSONArray("data");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decryptedJson = "";
for (int i = 0; i < chunks.length(); i++) {
byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
decryptedJson += decryptedChunk;
}
NetworkPackage decrypted = NetworkPackage.unserialize(decryptedJson);
decrypted.setPayload(np.getPayload(), np.getPayloadSize());
return decrypted;
}
}

View File

@@ -0,0 +1,264 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Helpers.SecurityHelpers;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.spongycastle.asn1.x500.X500NameBuilder;
import org.spongycastle.asn1.x500.style.BCStyle;
import org.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cert.X509v3CertificateBuilder;
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.kde.kdeconnect.Helpers.RandomHelper;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Formatter;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class SslHelper {
public enum SslMode{
Client,
Server
}
public static X509Certificate certificate; //my device's certificate
public static final BouncyCastleProvider BC = new BouncyCastleProvider();
public static void initialiseCertificate(Context context){
PrivateKey privateKey;
PublicKey publicKey;
try {
privateKey = RsaHelper.getPrivateKey(context);
publicKey = RsaHelper.getPublicKey(context);
}catch (Exception e){
Log.e("SslHelper", "Error getting keys, can't create certificate");
return;
}
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
if (!settings.contains("certificate")) {
try {
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, DeviceHelper.getDeviceId(context));
nameBuilder.addRDN(BCStyle.OU, "KDE Connect");
nameBuilder.addRDN(BCStyle.O, "KDE");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, -1);
Date notBefore = calendar.getTime();
calendar.add(Calendar.YEAR, 10);
Date notAfter = calendar.getTime();
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
nameBuilder.build(),
BigInteger.ONE,
notBefore,
notAfter,
nameBuilder.build(),
publicKey
);
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privateKey);
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner));
SharedPreferences.Editor edit = settings.edit();
edit.putString("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
edit.apply();
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/initialiseCert", "Exception");
return;
}
} else {
try {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] certificateBytes = Base64.decode(globalSettings.getString("certificate", ""), 0);
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
} catch (Exception e) {
Log.e("KDE/SslHelper", "Exception reading own certificate");
e.printStackTrace();
}
}
}
public static SSLContext getSslContext(Context context, String deviceId, boolean isDeviceTrusted) {
try {
// Get device private key
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
// Get remote device certificate if trusted
X509Certificate remoteDeviceCertificate = null;
if (isDeviceTrusted){
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
byte[] certificateBytes = Base64.decode(devicePreferences.getString("certificate", ""), 0);
Log.e("DeviceCertificate", "bytes:"+ Arrays.toString(certificateBytes));
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
remoteDeviceCertificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
}
// Setup keystore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setKeyEntry("key", privateKey, "".toCharArray(), new Certificate[]{certificate});
// Set certificate if device trusted
if (remoteDeviceCertificate != null){
keyStore.setCertificateEntry(deviceId, remoteDeviceCertificate);
}
// Setup key manager factory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "".toCharArray());
// Setup default trust manager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
// Setup custom trust manager if device not trusted
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext tlsContext = SSLContext.getInstance("TLSv1"); //Newer TLS versions are only supported on API 16+
if (isDeviceTrusted) {
tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
}else {
tlsContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, RandomHelper.secureRandom);
}
return tlsContext;
} catch (Exception e) {
Log.e("KDE/SslHelper", "Error creating tls context");
e.printStackTrace();
}
return null;
}
public static SSLEngine getSslEngine(final Context context, final String deviceId, SslMode sslMode) {
try{
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
final boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
SSLContext tlsContext = getSslContext(context, deviceId, isDeviceTrusted);
SSLEngine sslEngine = tlsContext.createSSLEngine();
sslEngine.setEnabledProtocols(new String[]{ "TLSv1" }); //Newer TLS versions are only supported on API 16+
// These cipher suites are most common of them that are accepted by kde and android during handshake
ArrayList<String> supportedCiphers = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
supportedCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
supportedCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
}
// Following ciphers are for and due to old devices
supportedCiphers.add("SSL_RSA_WITH_RC4_128_SHA");
supportedCiphers.add("SSL_RSA_WITH_RC4_128_MD5");
sslEngine.setEnabledCipherSuites(supportedCiphers.toArray(new String[supportedCiphers.size()]));
if (sslMode == SslMode.Client){
sslEngine.setUseClientMode(true);
}else{
sslEngine.setUseClientMode(false);
if (isDeviceTrusted) {
sslEngine.setNeedClientAuth(true);
}else {
sslEngine.setWantClientAuth(true);
}
}
return sslEngine;
}catch (Exception e){
e.printStackTrace();
Log.e("SslHelper", "Error creating ssl filter");
}
return null;
}
public static String getCertificateHash(Certificate certificate) {
try {
byte[] hash = MessageDigest.getInstance("SHA-1").digest(certificate.getEncoded());
Formatter formatter = new Formatter();
int i;
for (i = 0; i < hash.length-1; i++) {
formatter.format("%02x:", hash[i]);
}
formatter.format("%02x", hash[i]);
return formatter.toString();
} catch (Exception e) {
return null;
}
}
public static Certificate parseCertificate(byte[] certificateBytes) throws IOException, CertificateException {
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
}
}

View File

@@ -21,10 +21,8 @@
package org.kde.kdeconnect;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import org.json.JSONArray;
@@ -34,34 +32,19 @@ import org.kde.kdeconnect.Helpers.DeviceHelper;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import javax.crypto.Cipher;
public class NetworkPackage {
public final static int ProtocolVersion = 5;
public final static int ProtocolVersion = 6;
//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";
public static final String PACKAGE_TYPE_CAPABILITIES = "kdeconnect.capabilities";
private long mId;
private String mType;
String mType;
private JSONObject mBody;
private InputStream mPayload;
private JSONObject mPayloadTransferInfo;
@@ -140,109 +123,37 @@ public class NetworkPackage {
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); }
public String serialize() {
public String serialize() throws JSONException {
JSONObject jo = new JSONObject();
try {
jo.put("id", mId);
jo.put("type", mType);
jo.put("body", mBody);
if (hasPayload()) {
jo.put("payloadSize", mPayloadSize);
jo.put("payloadTransferInfo", mPayloadTransferInfo);
}
} catch(Exception e) {
e.printStackTrace();
Log.e("NetworkPackage", "Serialization exception");
jo.put("id", mId);
jo.put("type", mType);
jo.put("body", mBody);
if (hasPayload()) {
jo.put("payloadSize", mPayloadSize);
jo.put("payloadTransferInfo", mPayloadTransferInfo);
}
//QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
String json = jo.toString().replace("\\/","/")+"\n";
if (!isEncrypted()) {
//Log.e("NetworkPackage.serialize", json);
}
return json;
}
static public NetworkPackage unserialize(String s) {
static public NetworkPackage unserialize(String s) throws JSONException {
NetworkPackage np = new NetworkPackage();
try {
JSONObject jo = new JSONObject(s);
np.mId = jo.getLong("id");
np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body");
if (jo.has("payloadSize")) {
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
np.mPayloadSize = jo.getLong("payloadSize");
} else {
np.mPayloadTransferInfo = new JSONObject();
np.mPayloadSize = 0;
}
} catch (Exception e) {
e.printStackTrace();
Log.e("NetworkPackage", "Unserialization exception unserializing "+s);
return null;
JSONObject jo = new JSONObject(s);
np.mId = jo.getLong("id");
np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body");
if (jo.has("payloadSize")) {
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
np.mPayloadSize = jo.getLong("payloadSize");
} else {
np.mPayloadTransferInfo = new JSONObject();
np.mPayloadSize = 0;
}
if (!np.isEncrypted()) {
//Log.e("NetworkPackage.unserialize", s);
}
return np;
}
public NetworkPackage encrypt(PublicKey publicKey) throws GeneralSecurityException {
String serialized = serialize();
int chunkSize = 128;
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
JSONArray chunks = new JSONArray();
while (serialized.length() > 0) {
if (serialized.length() < chunkSize) {
chunkSize = serialized.length();
}
String chunk = serialized.substring(0, chunkSize);
serialized = serialized.substring(chunkSize);
byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
byte[] encryptedChunk;
encryptedChunk = cipher.doFinal(chunkBytes);
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
}
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED);
encrypted.set("data", chunks);
encrypted.setPayload(mPayload, mPayloadSize);
return encrypted;
}
public NetworkPackage decrypt(PrivateKey privateKey) throws GeneralSecurityException, JSONException {
JSONArray chunks = mBody.getJSONArray("data");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decryptedJson = "";
for (int i = 0; i < chunks.length(); i++) {
byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
decryptedJson += decryptedChunk;
}
NetworkPackage decrypted = unserialize(decryptedJson);
decrypted.setPayload(mPayload, mPayloadSize);
return decrypted;
}
static public NetworkPackage createIdentityPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
@@ -262,21 +173,6 @@ public class NetworkPackage {
}
static public NetworkPackage createPublicKeyPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", true);
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
np.set("publicKey", publicKey);
return np;
}
public void setPayload(byte[] data) {
setPayload(new ByteArrayInputStream(data), data.length);
}

View File

@@ -33,6 +33,9 @@ import org.kde.kdeconnect_tp.R;
public class BatteryPlugin extends Plugin {
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
public final static String PACKAGE_TYPE_BATTERY_REQUEST = "kdeconnect.battery.request";
// keep these fields in sync with kdeconnect-kded:BatteryPlugin.h:ThresholdBatteryEvent
private static final int THRESHOLD_EVENT_NONE= 0;
private static final int THRESHOLD_EVENT_BATTERY_LOW = 1;
@@ -72,7 +75,7 @@ public class BatteryPlugin extends Plugin {
} else {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_BATTERY);
np.set("currentCharge", currentCharge);
np.set("isCharging", isCharging);
np.set("thresholdEvent", thresholdEvent);
@@ -99,7 +102,6 @@ public class BatteryPlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false;
if (np.getBoolean("request")) {
if (lastInfo != null) {
@@ -110,4 +112,16 @@ public class BatteryPlugin extends Plugin {
return true;
}
@Override
public String[] getSupportedPackageTypes() {
String[] packetTypes = {PACKAGE_TYPE_BATTERY_REQUEST};
return packetTypes;
}
@Override
public String[] getOutgoingPackageTypes() {
String[] packetTypes = {PACKAGE_TYPE_BATTERY};
return packetTypes;
}
}

View File

@@ -20,13 +20,18 @@
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.Context;
import android.content.ClipboardManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ClipboardListener {
@@ -42,28 +47,33 @@ public class ClipboardListener {
return;
}
cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void onPrimaryClipChanged() {
try {
public void run() {
cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() {
try {
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
String content = item.coerceToText(context).toString();
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
String content = item.coerceToText(context).toString();
if (!content.equals(currentContent)) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CLIPBOARD);
np.set("content", content);
device.sendPackage(np);
currentContent = content;
if (!content.equals(currentContent)) {
NetworkPackage np = new NetworkPackage(ClipboardPlugin.PACKAGE_TYPE_CLIPBOARD);
np.set("content", content);
device.sendPackage(np);
currentContent = content;
}
} catch (Exception e) {
//Probably clipboard was not text
}
}
} catch(Exception e) {
//Probably clipboard was not text
}
};
cm.addPrimaryClipChangedListener(listener);
}
};
cm.addPrimaryClipChangedListener(listener);
});
}
public void stop() {

View File

@@ -32,6 +32,8 @@ import org.kde.kdeconnect_tp.R;
public class ClipboardPlugin extends Plugin {
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_clipboard);
@@ -63,13 +65,23 @@ public class ClipboardPlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_CLIPBOARD)) {
return false;
}
String content = np.getString("content");
listener.setText(content);
return true;
}
@Override
public String[] getSupportedPackageTypes() {
String[] packetTypes = {PACKAGE_TYPE_CLIPBOARD};
return packetTypes;
}
@Override
public String[] getOutgoingPackageTypes() {
String[] packetTypes = {PACKAGE_TYPE_CLIPBOARD};
return packetTypes;
}
}

View File

@@ -0,0 +1,55 @@
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
import android.app.Activity;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import org.kde.kdeconnect_tp.R;
public class FindMyPhoneActivity extends Activity {
Ringtone ringtone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_my_phone);
Window window = this.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
ringtone = RingtoneManager.getRingtone(getApplicationContext(), ringtoneUri);
if (android.os.Build.VERSION.SDK_INT >= 21) {
AudioAttributes.Builder b = new AudioAttributes.Builder();
b.setUsage(AudioAttributes.USAGE_ALARM);
ringtone.setAudioAttributes(b.build());
} else {
ringtone.setStreamType(AudioManager.STREAM_ALARM);
}
ringtone.play();
findViewById(R.id.bFindMyPhone).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
@Override
public void finish() {
ringtone.stop();
super.finish();
}
}

View File

@@ -0,0 +1,53 @@
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
/**
* Created by vineet on 1/11/14.
* and David Edmundson 2015
*/
public class FindMyPhonePlugin extends Plugin {
public final static String PACKAGE_TYPE_FINDMYPHONE = "kdeconnect.findmyphone";
public final static String PACKAGE_TYPE_FINDMYPHONE_REQUEST = "kdeconnect.findmyphone.request";
@Override
public String getDisplayName() {
return context.getString(R.string.findmyphone_title);
}
@Override
public String getDescription() {
return context.getString(R.string.findmyphone_description);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
Intent intent = new Intent(context,FindMyPhoneActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_FINDMYPHONE_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[0];
}
}

View File

@@ -101,7 +101,7 @@ public class KeyListenerView extends View {
}
public void sendChars(CharSequence chars) {
final NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
final NetworkPackage np = new NetworkPackage(MousePadPlugin.PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("key", chars.toString());
sendKeyPressPackage(np);
}
@@ -135,7 +135,7 @@ public class KeyListenerView extends View {
//Log.e("KeyDown", "utfChar:" + (char)event.getUnicodeChar());
//Log.e("KeyDown", "intUtfChar:" + event.getUnicodeChar());
final NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
final NetworkPackage np = new NetworkPackage(MousePadPlugin.PACKAGE_TYPE_MOUSEPAD_REQUEST);
boolean modifier = false;
if (event.isAltPressed()) {

View File

@@ -40,7 +40,6 @@ import org.kde.kdeconnect.Device;
import org.kde.kdeconnect_tp.R;
public class MousePadActivity extends ActionBarActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener {
String deviceId;
private final static float MinDistanceToSendScroll = 2.5f;
@@ -49,6 +48,8 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
private float mPrevY;
private float mCurrentX;
private float mCurrentY;
private float mCurrentSensitivity;
private int scrollDirection = 1;
boolean isScrolling = false;
float accumulatedDistanceY = 0;
@@ -89,14 +90,41 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
keyListenerView.setDeviceId(deviceId);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getBoolean(getString(R.string.mousepad_scroll_direction),false)) {
scrollDirection = -1;
} else {
scrollDirection = 1;
}
String doubleTapSetting = prefs.getString(getString(R.string.mousepad_double_tap_key),
getString(R.string.mousepad_double_default));
String tripleTapSetting = prefs.getString(getString(R.string.mousepad_triple_tap_key),
getString(R.string.mousepad_triple_default));
String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key),
getString(R.string.mousepad_sensitivity_default));
doubleTapAction = ClickType.fromString(doubleTapSetting);
tripleTapAction = ClickType.fromString(tripleTapSetting);
switch (sensitivitySetting){
case "slowest":
mCurrentSensitivity = 0.2f;
break;
case "aboveSlowest":
mCurrentSensitivity = 0.5f;
break;
case "default":
mCurrentSensitivity = 1.0f;
break;
case "aboveDefault":
mCurrentSensitivity = 1.5f;
break;
case "fastest":
mCurrentSensitivity = 2.0f;
break;
default:
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
final View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@@ -184,7 +212,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
if (mousePadPlugin == null) return;
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY);
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY, mCurrentSensitivity);
mPrevX = mCurrentX;
mPrevY = mCurrentY;
}
@@ -229,7 +257,7 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
if (mousePadPlugin == null) return;
mousePadPlugin.sendScroll(0, scrollToSendY);
mousePadPlugin.sendScroll(0, scrollDirection * scrollToSendY);
}
});

View File

@@ -31,6 +31,9 @@ import org.kde.kdeconnect_tp.R;
public class MousePadPlugin extends Plugin {
//public final static String PACKAGE_TYPE_MOUSEPAD = "kdeconnect.mousepad";
public final static String PACKAGE_TYPE_MOUSEPAD_REQUEST = "kdeconnect.mousepad.request";
@Override
public String getDisplayName() {
return context.getString(R.string.pref_plugin_mousepad);
@@ -63,50 +66,62 @@ public class MousePadPlugin extends Plugin {
parentActivity.startActivity(intent);
}
@Override
public String[] getSupportedPackageTypes() {
return new String[0];
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_MOUSEPAD_REQUEST};
}
@Override
public String getActionName() {
return context.getString(R.string.open_mousepad);
}
public void sendMouseDelta(float dx, float dy) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
np.set("dx", dx);
np.set("dy", dy);
public void sendMouseDelta(float dx, float dy, float sensitivity) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("dx", dx*sensitivity);
np.set("dy", dy*sensitivity);
device.sendPackage(np);
}
public void sendSingleClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("singleclick", true);
device.sendPackage(np);
}
public void sendDoubleClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("doubleclick", true);
device.sendPackage(np);
}
public void sendMiddleClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("middleclick", true);
device.sendPackage(np);
}
public void sendRightClick() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("rightclick", true);
device.sendPackage(np);
}
public void sendSingleHold(){
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("singlehold", true);
device.sendPackage(np);
}
public void sendScroll(float dx, float dy) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MOUSEPAD);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_REQUEST);
np.set("scroll", true);
np.set("dx", dx);
np.set("dy", dy);

View File

@@ -89,7 +89,11 @@ public class MprisActivity extends ActionBarActivity {
@Override
public void run() {
String song = mpris.getCurrentSong();
((TextView) findViewById(R.id.now_playing_textview)).setText(song);
TextView nowPlaying = (TextView) findViewById(R.id.now_playing_textview);
if (!nowPlaying.getText().toString().equals(song)) {
nowPlaying.setText(song);
}
if (mpris.getLength() > -1 && mpris.getPosition() > -1 && !"spotify".equals(mpris.getPlayer().toLowerCase())) {
((TextView) findViewById(R.id.time_textview)).setText(milisToProgress(mpris.getLength()));
@@ -410,10 +414,13 @@ public class MprisActivity extends ActionBarActivity {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
if (mpris != null) {
positionSeek.setProgress((int) (mpris.getPosition()));
if (device != null) {
MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
if (mpris != null) {
positionSeek.setProgress((int) (mpris.getPosition()));
}
}
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
}
});
@@ -450,6 +457,7 @@ public class MprisActivity extends ActionBarActivity {
});
findViewById(R.id.now_playing_textview).setSelected(true);
}
@Override

View File

@@ -37,6 +37,9 @@ import java.util.HashMap;
public class MprisPlugin extends Plugin {
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
public final static String PACKAGE_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
private String player = "";
private boolean playing = false;
private String currentSong = "";
@@ -82,7 +85,7 @@ public class MprisPlugin extends Plugin {
}
public void sendAction(String player, String action) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set("action", action);
device.sendPackage(np);
@@ -92,14 +95,14 @@ public class MprisPlugin extends Plugin {
}
public void setVolume(int volume) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set("setVolume",volume);
device.sendPackage(np);
}
public void setPosition(int position) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set("SetPosition", position);
device.sendPackage(np);
@@ -108,7 +111,7 @@ public class MprisPlugin extends Plugin {
}
public void Seek(int offset) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set("Seek", offset);
device.sendPackage(np);
@@ -116,7 +119,6 @@ public class MprisPlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false;
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") || np.has("pos")) {
if (np.getString("player").equals(player)) {
@@ -170,6 +172,16 @@ public class MprisPlugin extends Plugin {
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[] {PACKAGE_TYPE_MPRIS};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[] {PACKAGE_TYPE_MPRIS_REQUEST};
}
public void setPlayerStatusUpdatedHandler(String id, Handler h) {
playerStatusUpdated.put(id, h);
@@ -241,13 +253,13 @@ public class MprisPlugin extends Plugin {
}
private void requestPlayerList() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("requestPlayerList",true);
device.sendPackage(np);
}
private void requestPlayerStatus() {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player",player);
np.set("requestNowPlaying",true);
np.set("requestVolume",true);

View File

@@ -80,11 +80,14 @@ public class NotificationReceiver extends NotificationListenerService {
public int onStartCommand(Intent intent, int flags, int startId) {
//Log.e("NotificationReceiver", "onStartCommand");
mutex.lock();
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
try {
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
}
callbacks.clear();
} finally {
mutex.unlock();
}
callbacks.clear();
mutex.unlock();
return Service.START_STICKY;
}
@@ -96,8 +99,11 @@ public class NotificationReceiver extends NotificationListenerService {
public static void RunCommand(Context c, final InstanceCallback callback) {
if (callback != null) {
mutex.lock();
callbacks.add(callback);
mutex.unlock();
try {
callbacks.add(callback);
} finally {
mutex.unlock();
}
}
Intent serviceIntent = new Intent(c, NotificationReceiver.class);
c.startService(serviceIntent);

View File

@@ -24,12 +24,19 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import org.kde.kdeconnect.Helpers.AppsHelper;
@@ -39,8 +46,14 @@ import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R;
import java.io.InputStream;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
public final static String PACKAGE_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
/*
private boolean sendIcons = false;
*/
@@ -77,54 +90,52 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public boolean onCreate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
if (hasPermission()) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
try {
service.addListener(NotificationsPlugin.this);
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
sendNotification(notification, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (hasPermission()) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
try {
service.addListener(NotificationsPlugin.this);
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
sendNotification(notification, true);
}
} catch (Exception e) {
Log.e("NotificationsPlugin", "Exception");
e.printStackTrace();
}
} catch (Exception e) {
Log.e("NotificationsPlugin", "Exception");
e.printStackTrace();
}
}
});
return true;
} else {
return false;
});
} else {
return false;
}
}
// request all existing notifications
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
np.set("request", true);
device.sendPackage(np);
return true;
}
@Override
public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return;
}
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
service.removeListener(NotificationsPlugin.this);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
service.removeListener(NotificationsPlugin.this);
}
});
}
@Override
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
String id = getNotificationKeyCompat(statusBarNotification);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
np.set("id", id);
np.set("isCancel", true);
device.sendPackage(np);
@@ -166,12 +177,19 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
return;
}
if ("com.android.systemui".equals(packageName) &&
"low_battery".equals(statusBarNotification.getTag()))
{
//HACK: Android low battery notification are posted again every few seconds. Ignore them, as we already have a battery indicator.
return;
}
if (packageName.equals("com.google.android.googlequicksearchbox")) {
//HACK: Hide Google Now notifications that keep constantly popping up (and without text because we don't know how to read them properly)
return;
}
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION);
if (packageName.equals("org.kde.kdeconnect_tp"))
{
@@ -254,7 +272,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public boolean onPackageReceived(final NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
/*
if (np.getBoolean("sendIcons")) {
sendIcons = true;
@@ -262,53 +279,103 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
*/
if (np.getBoolean("request")) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
private void sendCurrentNotifications(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
sendNotification(notification, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
private void sendCurrentNotifications(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
sendNotification(notification, true);
}
}
}
@Override
public void onServiceStart(final NotificationReceiver service) {
try {
//If service just started, this call will throw an exception because the answer is not ready yet
sendCurrentNotifications(service);
} catch(Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying in 100ms...");
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
sendCurrentNotifications(service);
} catch (Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
e.printStackTrace();
@Override
public void onServiceStart(final NotificationReceiver service) {
try {
//If service just started, this call will throw an exception because the answer is not ready yet
sendCurrentNotifications(service);
} catch(Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying in 100ms...");
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
sendCurrentNotifications(service);
} catch (Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
e.printStackTrace();
}
}
}
}).start();
}).start();
}
}
}
});
});
} else if (np.has("cancel")) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
String dismissedId = np.getString("cancel");
cancelNotificationCompat(service, dismissedId);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
String dismissedId = np.getString("cancel");
cancelNotificationCompat(service, dismissedId);
}
});
} else {
if (!np.has("ticker") || !np.has("appName") || !np.has("id")) {
Log.e("NotificationsPlugin", "Received notification package lacks properties");
} else {
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MaterialActivity.class);
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Log.w("NotificationsPlugin", "Nothing to do");
Bitmap largeIcon = null;
if (np.hasPayload()) {
int width = 64; // default icon dimensions
int height = 64;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
}
final InputStream input = np.getPayload();
largeIcon = BitmapFactory.decodeStream(np.getPayload());
try { input.close(); } catch (Exception e) { }
if (largeIcon != null) {
//Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height);
if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) {
// older API levels don't scale notification icons automatically, therefore:
largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false);
}
}
}
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(np.getString("appName"))
.setContentText(np.getString("ticker"))
.setContentIntent(resultPendingIntent)
.setTicker(np.getString("ticker"))
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(largeIcon)
.setAutoCancel(true)
.setLocalOnly(true) // to avoid bouncing the notification back to other kdeconnect nodes
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
try {
// tag all incoming notifications
notificationManager.notify("kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
} catch (Exception e) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
}
}
return true;
@@ -350,7 +417,17 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
}
//For compat with API<21, because lollipop changed they way to cancel notifications
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_NOTIFICATION, PACKAGE_TYPE_NOTIFICATION_REQUEST};
}
//For compat with API<21, because lollipop changed the way to cancel notifications
public static void cancelNotificationCompat(NotificationReceiver service, String compatKey) {
if (Build.VERSION.SDK_INT >= 21) {
service.cancelNotification(compatKey);
@@ -372,16 +449,20 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
public static String getNotificationKeyCompat(StatusBarNotification statusBarNotification) {
if (Build.VERSION.SDK_INT >= 21) {
return statusBarNotification.getKey();
String result;
// first check if it's one of our remoteIds
String tag = statusBarNotification.getTag();
if (tag != null && tag.startsWith("kdeconnectId:"))
result = Integer.toString(statusBarNotification.getId());
else if (Build.VERSION.SDK_INT >= 21) {
result = statusBarNotification.getKey();
} else {
String packageName = statusBarNotification.getPackageName();
String tag = statusBarNotification.getTag();
int id = statusBarNotification.getId();
String safePackageName = (packageName == null) ? "" : packageName;
String safeTag = (tag == null) ? "" : tag;
return safePackageName + ":" + safeTag + ":" + id;
result = safePackageName + ":" + safeTag + ":" + id;
}
return result;
}
}

View File

@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
@@ -37,6 +38,8 @@ import org.kde.kdeconnect_tp.R;
public class PingPlugin extends Plugin {
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_ping);
@@ -50,50 +53,51 @@ public class PingPlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
//Log.e("PingPackageReceiver", "onPackageReceived");
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
//Log.e("PingPackageReceiver", "was a ping!");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MaterialActivity.class);
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
int id;
String message;
if (np.has("message")) {
message = np.getString("message");
id = (int)System.currentTimeMillis();
} else {
message = "Ping!";
id = 42; //A unique id to create only one notification
}
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName())
.setContentText(message)
.setContentIntent(resultPendingIntent)
.setTicker(message)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
try {
notificationManager.notify(id, noti);
} catch(Exception e) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
return true;
if (!np.getType().equals(PACKAGE_TYPE_PING)) {
Log.e("PingPlugin", "Ping plugin should not receive packets other than pings!");
return false;
}
return false;
//Log.e("PingPackageReceiver", "was a ping!");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MaterialActivity.class);
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
int id;
String message;
if (np.has("message")) {
message = np.getString("message");
id = (int)System.currentTimeMillis();
} else {
message = "Ping!";
id = 42; //A unique id to create only one notification
}
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName())
.setContentText(message)
.setContentIntent(resultPendingIntent)
.setTicker(message)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
try {
notificationManager.notify(id, noti);
} catch(Exception e) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
return true;
}
@Override
@@ -104,7 +108,7 @@ public class PingPlugin extends Plugin {
@Override
public void startMainActivity(Activity activity) {
if (device != null) {
device.sendPackage(new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING));
device.sendPackage(new NetworkPackage(PACKAGE_TYPE_PING));
}
}
@@ -117,4 +121,15 @@ public class PingPlugin extends Plugin {
public boolean displayInContextMenu() {
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_PING};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_PING};
}
}

View File

@@ -174,6 +174,16 @@ public abstract class Plugin {
return null;
}
/**
* Should return the list of NetworkPackage types that this plugin can handle
*/
public abstract String[] getSupportedPackageTypes();
/**
* Should return the list of NetworkPackage types that this plugin can send
*/
public abstract String[] getOutgoingPackageTypes();
/**
* Creates a button that will be displayed in the user interface
* It can open an activity or perform any other action that the

View File

@@ -26,15 +26,21 @@ import android.util.Log;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
import org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhonePlugin;
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
import org.kde.kdeconnect.Plugins.TelepathyPlugin.TelepathyPlugin;
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -45,13 +51,20 @@ public class PluginFactory {
public static class PluginInfo {
public PluginInfo(String displayName, String description, Drawable icon,
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired) {
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired,
String[] supportedPackageTypes, String[] outgoingPackageTypes) {
this.displayName = displayName;
this.description = description;
this.icon = icon;
this.enabledByDefault = enabledByDefault;
this.hasSettings = hasSettings;
this.listenToUnpaired = listenToUnpaired;
HashSet<String> incoming = new HashSet<>();
if (supportedPackageTypes != null) Collections.addAll(incoming, supportedPackageTypes);
this.supportedPackageTypes = Collections.unmodifiableSet(incoming);
HashSet<String> outgoing = new HashSet<>();
if (outgoingPackageTypes != null) Collections.addAll(outgoing, outgoingPackageTypes);
this.outgoingPackageTypes = Collections.unmodifiableSet(outgoing);
}
public String getDisplayName() {
@@ -76,12 +89,23 @@ public class PluginFactory {
return listenToUnpaired;
}
public Set<String> getOutgoingPackageTypes() {
return outgoingPackageTypes;
}
public Set<String> getSupportedPackageTypes() {
return supportedPackageTypes;
}
private final String displayName;
private final String description;
private final Drawable icon;
private final boolean enabledByDefault;
private final boolean hasSettings;
private final boolean listenToUnpaired;
private final Set<String> supportedPackageTypes;
private final Set<String> outgoingPackageTypes;
}
private static final Map<String, Class> availablePlugins = new TreeMap<>();
@@ -98,16 +122,24 @@ public class PluginFactory {
PluginFactory.registerPlugin(NotificationsPlugin.class);
PluginFactory.registerPlugin(MousePadPlugin.class);
PluginFactory.registerPlugin(SharePlugin.class);
//PluginFactory.registerPlugin(TelepathyPlugin.class);
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
PluginFactory.registerPlugin(RunCommandPlugin.class);
}
public static PluginInfo getPluginInfo(Context context, String pluginKey) {
PluginInfo info = availablePluginsInfo.get(pluginKey); //Is it cached?
if (info != null) return info;
if (info != null) {
return info;
}
try {
Plugin p = ((Plugin)availablePlugins.get(pluginKey).newInstance());
p.setContext(context, null);
info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(),
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices());
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices(),
p.getSupportedPackageTypes(), p.getOutgoingPackageTypes());
availablePluginsInfo.put(pluginKey, info); //Cache it
return info;
} catch(Exception e) {
@@ -115,6 +147,7 @@ public class PluginFactory {
e.printStackTrace();
return null;
}
}
public static Set<String> getAvailablePlugins() {

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.UserInterface.List.EntryItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
public class RunCommandActivity extends ActionBarActivity {
private String deviceId;
private void updateView() {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
final Device device = service.getDevice(deviceId);
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
if (plugin == null) {
Log.e("RunCommandActivity", "device has no runcommand plugin!");
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
ListView view = (ListView) findViewById(R.id.listView1);
final ArrayList<JSONObject> commands = plugin.getCommandList();
ArrayList<ListAdapter.Item> commandItems = new ArrayList<>();
for (JSONObject obj : commands) {
try {
commandItems.add(new EntryItem(obj.getString("name"), obj.getString("command")));
} catch (JSONException e) {
e.printStackTrace();
}
}
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
view.setAdapter(adapter);
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
try {
plugin.runCommand(commands.get(i).getString("key"));
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
});
}
private final RunCommandPlugin.CommandsChangedCallback theCallback = new RunCommandPlugin.CommandsChangedCallback() {
@Override
public void update() {
updateView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
deviceId = getIntent().getStringExtra("deviceId");
updateView();
}
@Override
protected void onResume() {
super.onResume();
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
final Device device = service.getDevice(deviceId);
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
if (plugin == null) {
Log.e("RunCommandActivity", "device has no runcommand plugin!");
return;
}
plugin.addCommandsUpdatedCallback(theCallback);
}
});
}
@Override
protected void onPause() {
super.onPause();
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
final Device device = service.getDevice(deviceId);
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
if (plugin == null) {
Log.e("RunCommandActivity", "device has no runcommand plugin!");
return;
}
plugin.removeCommandsUpdatedCallback(theCallback);
}
});
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
* Copyright 2015 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Iterator;
public class RunCommandPlugin extends Plugin {
public final static String PACKAGE_TYPE_RUNCOMMAND = "kdeconnect.runcommand";
public final static String PACKAGE_TYPE_RUNCOMMAND_REQUEST = "kdeconnect.runcommand.request";
private ArrayList<JSONObject> commandList = new ArrayList<>();
private ArrayList<CommandsChangedCallback> callbacks = new ArrayList<>();
public void addCommandsUpdatedCallback(CommandsChangedCallback newCallback) {
callbacks.add(newCallback);
}
public void removeCommandsUpdatedCallback(CommandsChangedCallback theCallback) {
callbacks.remove(theCallback);
}
interface CommandsChangedCallback {
void update();
};
public ArrayList<JSONObject> getCommandList() {
return commandList;
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_runcommand);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_runcommand_desc);
}
@Override
public Drawable getIcon() {
return ContextCompat.getDrawable(context, R.drawable.runcommand_plugin_icon);
}
@Override
public boolean onCreate() {
requestCommandList();
return true;
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (np.has("commandList")) {
commandList.clear();
try {
JSONObject obj = new JSONObject(np.getString("commandList"));
Iterator<String> keys = obj.keys();
while(keys.hasNext()){
String s = keys.next();
JSONObject o = obj.getJSONObject(s);
o.put("key", s);
commandList.add(o);
}
} catch (JSONException e) {
e.printStackTrace();
}
for (CommandsChangedCallback aCallback : callbacks) {
aCallback.update();
}
device.onPluginsChanged();
return true;
}
return false;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_RUNCOMMAND};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_RUNCOMMAND_REQUEST};
}
public void runCommand(String cmdKey) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
np.set("key", cmdKey);
device.sendPackage(np);
}
private void requestCommandList() {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
np.set("requestCommandList", true);
device.sendPackage(np);
}
@Override
public boolean hasMainActivity() {
return !commandList.isEmpty();
}
@Override
public void startMainActivity(Activity parentActivity) {
Intent intent = new Intent(parentActivity, RunCommandActivity.class);
intent.putExtra("deviceId", device.getDeviceId());
parentActivity.startActivity(intent);
}
@Override
public String getActionName() {
return context.getString(R.string.pref_plugin_runcommand);
}
}

View File

@@ -23,10 +23,11 @@ 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.KeyExchange;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
@@ -34,18 +35,24 @@ import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.filesystem.NativeFileSystemView;
import org.apache.sshd.server.filesystem.NativeSshFile;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.RandomHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import java.io.File;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.PublicKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -100,27 +107,37 @@ class SimpleSftpServer {
public final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
public final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
static {
Security.insertProviderAt( SslHelper.BC, 1);
SecurityUtils.setRegisterBouncyCastle(false);
}
private final SshServer sshd = SshServer.setUpDefaultServer();
public void init(Context ctx, Device device) {
sshd.setKeyExchangeFactories(Arrays.asList(
new DHG14.Factory(),
new DHG1.Factory()));
passwordAuth.setUser(USER);
keyAuth.addKey(device.publicKey);
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(ctx.getFilesDir() + "/sftpd.ser"));
//sshd.setFileSystemFactory(new NativeFileSystemFactory());
sshd.setFileSystemFactory(new SecureFileSystemFactory());
//sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setSubsystemFactories(Collections.singletonList((NamedFactory<Command>)new SftpSubsystem.Factory()));
sshd.setPasswordAuthenticator(passwordAuth);
sshd.setPublickeyAuthenticator(keyAuth);
if (device.publicKey != null) {
keyAuth.addKey(device.publicKey);
sshd.setPublickeyAuthenticator(keyAuth);
} else {
sshd.setPasswordAuthenticator(passwordAuth);
}
}
public boolean start() {
if (!started) {
String password = Long.toHexString(Double.doubleToLongBits(Math.random()));
String password = RandomHelper.randomString(28);
passwordAuth.setPassword(password);
port = STARTPORT;
@@ -130,6 +147,7 @@ class SimpleSftpServer {
sshd.start();
started = true;
} catch(Exception e) {
e.printStackTrace();
port++;
if (port >= ENDPORT) {
port = -1;
@@ -147,8 +165,8 @@ class SimpleSftpServer {
try {
started = false;
sshd.stop();
} catch (InterruptedException e) {
} catch (Exception e) {
e.printStackTrace();
}
}
@@ -161,7 +179,7 @@ class SimpleSftpServer {
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
if(inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
return address;
} else {
ip6 = address;

View File

@@ -33,6 +33,9 @@ import java.util.List;
public class SftpPlugin extends Plugin {
public final static String PACKAGE_TYPE_SFTP = "kdeconnect.sftp";
public final static String PACKAGE_TYPE_SFTP_REQUEST = "kdeconnect.sftp.request";
private static final SimpleSftpServer server = new SimpleSftpServer();
@Override
@@ -58,12 +61,11 @@ public class SftpPlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SFTP)) return false;
if (np.getBoolean("startBrowsing")) {
if (server.start()) {
NetworkPackage np2 = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SFTP);
NetworkPackage np2 = new NetworkPackage(PACKAGE_TYPE_SFTP);
np2.set("ip", server.getLocalIpAddress());
np2.set("port", server.port);
@@ -127,4 +129,14 @@ public class SftpPlugin extends Plugin {
return false;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SFTP_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_SFTP};
}
}

View File

@@ -170,7 +170,7 @@ public class ShareActivity extends ActionBarActivity {
} catch (Exception e) {
isUrl = false;
}
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE);
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE);
if (isUrl) {
np.set("url", text);
} else {

View File

@@ -58,6 +58,9 @@ import java.util.ArrayList;
public class SharePlugin extends Plugin {
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
final static boolean openUrlsDirectly = true;
@Override
@@ -100,10 +103,6 @@ public class SharePlugin extends Plugin {
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SHARE)) {
return false;
}
try {
if (np.hasPayload()) {
@@ -311,7 +310,7 @@ public class SharePlugin extends Plugin {
ContentResolver cr = context.getContentResolver();
InputStream inputStream = cr.openInputStream(uri);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_SHARE_REQUEST);
long size = -1;
final NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -494,4 +493,15 @@ public class SharePlugin extends Plugin {
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
}

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