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

Compare commits

..

232 Commits

Author SHA1 Message Date
Albert Vaca
debbe4fe2c Increased version number to release 2014-10-20 01:31:54 -07:00
Albert Vaca
5724a66561 Added some extra safety checks 2014-10-20 01:30:02 -07:00
Albert Vaca
91fe9d63b0 Removed incorrect translation. 2014-10-20 01:14:45 -07:00
Albert Vaca
a90928188a Fixed crash. 2014-10-20 01:13:41 -07:00
Albert Vaca
256ca60036 Updated version number to release. 2014-10-18 21:19:15 -07:00
Albert Vaca
05fd6fe5b0 Extra safety check 2014-10-18 21:19:15 -07:00
Albert Vaca
6b9a691f24 Fixed warning 2014-10-18 21:19:15 -07:00
l10n daemon script
d18b61f2a1 SVN_SILENT made messages (after extraction) 2014-10-19 01:58:23 +00:00
Albert Vaca
6448258648 Wrong translations sneaked in again
Revert "SVN_SILENT made messages (after extraction)"

This reverts commit f3fbe4d035.
2014-10-17 19:12:36 -07:00
Albert Vaca
e5e9490f19 Resize window when the keyboard appears, so text is always centered. 2014-10-17 19:11:38 -07:00
Albert Vaca
9f7c5be533 Do not restart the activity when orientation changes. 2014-10-17 19:11:38 -07:00
Albert Vaca
f690105181 "Show keyboard" button now actually toggles it. 2014-10-17 19:11:38 -07:00
Albert Vaca
dafe9eb023 Keyboard won't use the full screen when in landscape mode. 2014-10-17 19:11:38 -07:00
Albert Vaca
cfbe6775d0 Linefeed was sent instead of return when return was pressed. 2014-10-17 19:11:38 -07:00
Albert Vaca
6365143c2c Added icon to open the keyboard from the action bar 2014-10-17 19:11:38 -07:00
l10n daemon script
f3fbe4d035 SVN_SILENT made messages (after extraction) 2014-10-15 01:56:22 +00:00
Albert Vaca
2862e5164e Oops 2014-10-14 01:31:51 -07:00
Albert Vaca
6fae1d350d Android Studio decided to change the order of some lines 2014-10-14 01:12:39 -07:00
Albert Vaca
647f71bf98 Removed incorrect translations to release 2014-10-13 21:56:21 -07:00
Albert Vaca
343b2ed5e8 Increased version number to release 2014-10-13 21:56:07 -07:00
l10n daemon script
eb7351a803 SVN_SILENT made messages (after extraction) 2014-10-14 02:00:37 +00:00
l10n daemon script
7e7d6c4d82 SVN_SILENT made messages (after extraction) 2014-10-13 01:59:04 +00:00
l10n daemon script
59d01763c7 SVN_SILENT made messages (after extraction) 2014-10-12 01:56:58 +00:00
Albert Vaca
5b2756b7f8 Added support for devices with more than one external storage
BUG: 336043
2014-10-10 15:49:27 -07:00
Albert Vaca
5912611aab Fixed onPluginsChanged not being always called after calling addPlugin 2014-10-10 12:38:11 -07:00
l10n daemon script
54d24848e5 SVN_SILENT made messages (after extraction) 2014-10-10 02:11:27 +00:00
l10n daemon script
638c9df328 SVN_SILENT made messages (after extraction) 2014-10-07 01:58:52 +00:00
Albert Vaca
9943c36877 Minor layout changes 2014-10-04 21:13:12 -07:00
Albert Vaca
03bcfecb88 Fixed previous commit to work on Android API < 19 2014-10-04 21:07:57 -07:00
Albert Vaca
1b4ade0ae1 MPRIS controls now adapt to tablet resolutions
BUG: 339684
2014-10-04 20:58:14 -07:00
l10n daemon script
b9890210ad SVN_SILENT made messages (after extraction) 2014-10-05 01:58:53 +00:00
l10n daemon script
1509bfaf6d SVN_SILENT made messages (after extraction) 2014-10-04 01:53:54 +00:00
Albert Vaca
a22ffd611a Simplified device activity layout to use a single ListView
This fixes some layout problems that prevented us of having the height of
the lists adapted automatically to the content and had to use fixed height.
2014-09-29 19:03:31 -07:00
Albert Vaca
adb88560b5 Minor changes 2014-09-29 19:03:31 -07:00
Albert Vaca
03ede77bd1 Highlight device when selected from the main activity list.
I had to include the style files, made with http://android-holo-colors.com
because, from kitkat on, the background color of highlighted elements is
gray instead of blue, but that's not reflected in the resources included
in the support library v7 appcompat jar, that are still blue.
2014-09-29 19:03:31 -07:00
Albert Vaca
6afbe93c2d Made error list header not selectable 2014-09-29 19:03:31 -07:00
l10n daemon script
dd4421dce2 SVN_SILENT made messages (after extraction) 2014-09-30 01:55:45 +00:00
Albert Vaca
262f811bba Implemented custom ping messages. 2014-09-26 20:45:28 -07:00
Albert Vaca
0b52cbe36f Fixed bug where plugin errors were not shown if all enabled plugins fail. 2014-09-26 20:45:28 -07:00
Albert Vaca
8542aaf4fa Fixed bug were list of enabled plugins didn't refresh itself.
Removed PreferenceListAdapter
2014-09-26 20:45:27 -07:00
l10n daemon script
c9d11f650c SVN_SILENT made messages (after extraction) 2014-09-27 02:00:12 +00:00
l10n daemon script
6c5ed3094b SVN_SILENT made messages (after extraction) 2014-09-26 02:01:30 +00:00
l10n daemon script
5aaec15e79 SVN_SILENT made messages (after extraction) 2014-09-24 01:50:30 +00:00
Albert Vaca
b0a632fe10 Fixed error list always empty the second time it is loaded. 2014-09-23 16:42:26 +02:00
Albert Vaca
4d1464484e Commented priority sort of links because we don't use it and causes crashes 2014-09-23 16:41:57 +02:00
Albert Vaca
d6cc84ccfb Fixed crash 2014-09-23 16:40:54 +02:00
Albert Vaca
624cee5d87 Workaround for bug where plugins are not loaded for a device 2014-09-23 16:40:34 +02:00
Albert Vaca
a8664d01ec Updated project files 2014-09-23 16:39:28 +02:00
l10n daemon script
bd68f71af6 SVN_SILENT made messages (after extraction) 2014-09-22 01:59:00 +00:00
Albert Vaca
8ec828d6e4 Reload plugins list when user gives permission to access notifications.
This way the error message disappears without having to reload the app.
2014-09-20 10:52:03 +02:00
Albert Vaca
a44d48d527 Separated arrays.xml and strings.xml were causing confusion to the script
that extracts the translations, because it expects a single output file.
2014-09-20 10:24:01 +02:00
Albert Vaca
804642c5fc Fixed bug causing some plugin settings having checkbox when they shouldn't
and viceversa.
2014-09-17 16:06:23 +02:00
Albert Vaca
8e0daa69cc Added preference to disable incoming file notification sound. 2014-09-17 16:05:54 +02:00
Albert Vaca
400b35216d Minor changes 2014-09-17 16:05:16 +02:00
Albert Vaca
4a5545529a Those static will never work
Because class statics are called the first time the class is instantiated.
2014-09-17 10:36:45 +02:00
Ronny Yabar Aizcorbe
b2fa8ab506 Plugin-specific settings
Added support for specific plugin settings.
Added interval time preference to fast forward or rewind a multimedia file
instead of hardcording the time.

REVIEW: 120005
2014-09-16 15:45:58 +02:00
l10n daemon script
b9a0b3d2f0 SVN_SILENT made messages (after extraction) 2014-09-11 01:59:39 +00:00
l10n daemon script
e8c6784ce0 SVN_SILENT made messages (after extraction) 2014-09-03 01:58:03 +00:00
l10n daemon script
e3b8bb5ffc SVN_SILENT made messages (after extraction) 2014-08-28 02:01:02 +00:00
l10n daemon script
103bc69347 SVN_SILENT made messages (after extraction) 2014-08-25 02:00:05 +00:00
l10n daemon script
2b45c82a2e SVN_SILENT made messages (after extraction) 2014-08-22 02:05:13 +00:00
l10n daemon script
0d3232a5a1 SVN_SILENT made messages (after extraction) 2014-08-20 01:58:04 +00:00
l10n daemon script
ed5ddc37c8 SVN_SILENT made messages (after extraction) 2014-08-18 02:03:00 +00:00
l10n daemon script
48bc56f52a SVN_SILENT made messages (after extraction) 2014-08-17 02:00:27 +00:00
l10n daemon script
5de10b05e7 SVN_SILENT made messages (after extraction) 2014-08-16 01:56:50 +00:00
Albert Vaca
1fe2590ec8 Changes to the detection of keyboard input.
Now it detects more special keys.
It does not detect modifiers anymore.
Moved to a view that keeps the focus and prevents focusing the back button.
Changed bitmasks for a hashmap to make it more readable with lots of keys.
Separated sendSpecialKey from sendKey for non-representable keys.

CCMAIL: saiarcot895@gmail.com
2014-08-14 21:50:19 +02:00
Saikrishna Arcot
d02684c714 Add support for using the keyboard.
This patch adds a menu entry to display the keyboard in the mousepad plugin
and allow the user to send keystrokes. Dead keys are not yet supported, and
behavior is unknown if entered.

REVIEW: 119255
2014-08-14 21:50:19 +02:00
Job Noorman
80ed8a58d5 Properly restore the selected MPRIS player after a plugin reconnect.
Whenever the link with a device goes down (the easiest way to test this is to
lock the phone's screen), the connection with the MPRIS plugin is
re-established when the link goes up again. This patch makes sure that the
currently selected player (the one stored on the MprisPlugin instance) is also
selected in the MprisActivity. Currently, the first player in the list is
always selected.

REVIEW: 119692
2014-08-13 22:52:52 +02:00
l10n daemon script
962501fe38 SVN_SILENT made messages (after extraction) 2014-08-07 01:48:38 +00:00
l10n daemon script
60c1e67e66 SVN_SILENT made messages (after extraction) 2014-07-25 02:14:34 +00:00
l10n daemon script
318b82941f SVN_SILENT made messages (after extraction) 2014-07-16 02:16:36 +00:00
Albert Vaca
acdf8d467e This has to be public for some reason 2014-07-12 20:03:08 +02:00
Albert Vaca
ceee1ee1f6 Increased version number to release 2014-07-12 20:03:08 +02:00
Albert Vaca
071f71cb9c Silly changes proposed by lint 2014-07-12 20:03:08 +02:00
Albert Vaca
4adf2cfbbd Updated project to new Android Build Tools. 2014-07-12 20:03:08 +02:00
l10n daemon script
de4fa31b2a SVN_SILENT made messages (after extraction) 2014-07-07 02:01:50 +00:00
l10n daemon script
4deba4ea4f SVN_SILENT made messages (after extraction) 2014-07-05 01:59:05 +00:00
l10n daemon script
07a8b107eb SVN_SILENT made messages (after extraction) 2014-07-03 01:51:49 +00:00
Ahmed I. Khalil
3b750c78ab Added Triple and Double finger gestures.
For right and middle clicking.

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

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

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

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

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

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

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

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

9
.gitignore vendored
View File

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

3
.reviewboardrc Normal file
View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -1,28 +0,0 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Context;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
public class PreferenceListAdapter extends ArrayAdapter<Preference> {
private ArrayList<Preference> localList;
public PreferenceListAdapter(Context context, ArrayList<Preference> items) {
super(context,0, items);
localList = items;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Preference preference = localList.get(position);
return preference.getView(convertView, parent);
}
}

View File

@@ -1,70 +0,0 @@
package org.kde.kdeconnect.UserInterface;
import android.app.ListActivity;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.PluginFactory;
import java.util.ArrayList;
import java.util.Set;
public class SettingsActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String deviceId = getIntent().getStringExtra("deviceId");
BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
final Device device = service.getDevice(deviceId);
Set<String> plugins = PluginFactory.getAvailablePlugins();
final ArrayList<Preference> preferences = new ArrayList<Preference>();
for (final String pluginName : plugins) {
CheckBoxPreference pref = new CheckBoxPreference(getBaseContext());
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(getBaseContext(), pluginName);
pref.setKey(pluginName);
pref.setTitle(info.getDisplayName());
pref.setSummary(info.getDescription());
pref.setChecked(device.isPluginEnabled(pluginName));
preferences.add(pref);
}
setListAdapter(new PreferenceListAdapter(SettingsActivity.this, preferences));
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
CheckBoxPreference pref = (CheckBoxPreference)preferences.get(i);
boolean enabled = device.isPluginEnabled(pref.getKey());
device.setPluginEnabled(pref.getKey(), !enabled);
pref.setChecked(!enabled);
getListAdapter().getView(i, view, null); //This will refresh the view (yes, this is the way to do it)
}
});
getListView().setPadding(16,16,16,16);
}
});
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,26 +0,0 @@
<LinearLayout 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=".MainActivity"
android:orientation="vertical">
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/buttons_list"
android:layout_weight="1"
/>
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/errors_list"
android:layout_weight="1"
/>
</LinearLayout>

View File

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

View File

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

21
StaticMessages.sh Normal file
View File

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

View File

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

View File

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

1067
icon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 35 KiB

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.kde.kdeconnect_tp" package="org.kde.kdeconnect_tp"
android:versionCode="5" android:versionCode="732"
android:versionName="0.3.0"> android:versionName="0.7.3.2">
<uses-sdk android:minSdkVersion="9" <uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="18" /> android:targetSdkVersion="19" />
<supports-screens <supports-screens
android:smallScreens="true" android:smallScreens="true"
@@ -18,13 +18,15 @@
<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.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <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.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:required="false"/> <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.BATTERY_STATS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false"/> <uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -44,11 +46,19 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.MainSettingsActivity"
android:label="@string/settings"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity <activity
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.DeviceActivity" android:name="org.kde.kdeconnect.UserInterface.DeviceActivity"
@@ -57,8 +67,7 @@
> >
<meta-data android:name="android.support.PARENT_ACTIVITY" <meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" /> android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
</activity>
<activity <activity
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
@@ -68,7 +77,6 @@
> >
<meta-data android:name="android.support.PARENT_ACTIVITY" <meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" /> android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity> </activity>
<activity <activity
@@ -80,6 +88,15 @@
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" /> android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity> </activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
android:label="@string/mpris_settings"
android:parentActivityName="org.kde.kdeconnect.UserInterface.SettingsActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.SettingsActivity" />
</activity>
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver"> <receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" /> <action android:name="android.intent.action.PACKAGE_REPLACED" />
@@ -117,6 +134,19 @@
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" /> android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity> </activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
android:label="@string/remote_control"
android:windowSoftInputMode="stateHidden|adjustResize"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="fullSensor"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity>
<activity <activity
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.ShareToReceiver" android:name="org.kde.kdeconnect.UserInterface.ShareToReceiver"

View File

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

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList;
public abstract class BaseLinkProvider { public abstract class BaseLinkProvider {
private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>(); private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public interface ConnectionReceiver { public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link); public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link);
@@ -43,7 +43,7 @@ public abstract class BaseLinkProvider {
public abstract void onStop(); public abstract void onStop();
public abstract void onNetworkChange(); public abstract void onNetworkChange();
public abstract int getPriority(); //public abstract int getPriority();
public abstract String getName(); public abstract String getName();
} }

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import org.kde.kdeconnect.NetworkPackage;
public class LoopbackLinkProvider extends BaseLinkProvider { public class LoopbackLinkProvider extends BaseLinkProvider {
private Context context; private final Context context;
public LoopbackLinkProvider(Context context) { public LoopbackLinkProvider(Context context) {
this.context = context; this.context = context;
@@ -20,22 +20,19 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
@Override @Override
public void onStop() { public void onStop() {
} }
@Override @Override
public void onNetworkChange() { public void onNetworkChange() {
NetworkPackage np = NetworkPackage.createIdentityPackage(context); NetworkPackage np = NetworkPackage.createIdentityPackage(context);
connectionAccepted(np, new LoopbackLink(this)); connectionAccepted(np, new LoopbackLink(this));
} }
/*
@Override @Override
public int getPriority() { public int getPriority() {
return 0; return 0;
} }
*/
@Override @Override
public String getName() { public String getName() {
return "LoopbackLinkProvider"; return "LoopbackLinkProvider";

View File

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

View File

@@ -7,7 +7,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@@ -36,11 +35,11 @@ import java.util.TimerTask;
public class Device implements BaseLink.PackageReceiver { public class Device implements BaseLink.PackageReceiver {
private Context context; private final Context context;
private String deviceId; private final String deviceId;
private String name; private final String name;
private PublicKey publicKey; public PublicKey publicKey;
private int notificationId; private int notificationId;
private int protocolVersion; private int protocolVersion;
@@ -62,11 +61,11 @@ public class Device implements BaseLink.PackageReceiver {
private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>(); private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>();
private Timer pairingTimer; private Timer pairingTimer;
private ArrayList<BaseLink> links = new ArrayList<BaseLink>(); private final ArrayList<BaseLink> links = new ArrayList<BaseLink>();
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>(); private final HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>(); private final HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
SharedPreferences settings; private final SharedPreferences settings;
//Remembered trusted device, we need to wait for a incoming devicelink to communicate //Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device(Context context, String deviceId) { Device(Context context, String deviceId) {
@@ -93,17 +92,18 @@ public class Device implements BaseLink.PackageReceiver {
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet //Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
Device(Context context, NetworkPackage np, BaseLink dl) { Device(Context context, NetworkPackage np, BaseLink dl) {
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
//Log.e("Device","Constructor B"); //Log.e("Device","Constructor B");
this.context = context; this.context = context;
this.deviceId = np.getString("deviceId"); this.deviceId = np.getString("deviceId");
this.name = np.getString("deviceName"); this.name = np.getString("deviceName", "unidentified device");
this.protocolVersion = np.getInt("protocolVersion"); this.protocolVersion = np.getInt("protocolVersion");
this.pairStatus = PairStatus.NotPaired; this.pairStatus = PairStatus.NotPaired;
this.publicKey = null; this.publicKey = null;
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
addLink(np, dl); addLink(np, dl);
} }
@@ -151,15 +151,21 @@ public class Device implements BaseLink.PackageReceiver {
Resources res = context.getResources(); Resources res = context.getResources();
if (pairStatus == PairStatus.Paired) { if (pairStatus == PairStatus.Paired) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_paired)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_paired));
}
return; return;
} }
if (pairStatus == PairStatus.Requested) { if (pairStatus == PairStatus.Requested) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_requested)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_requested));
}
return; return;
} }
if (!isReachable()) { if (!isReachable()) {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_not_reachable)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_not_reachable));
}
return; return;
} }
@@ -169,6 +175,7 @@ public class Device implements BaseLink.PackageReceiver {
@Override @Override
public void sendSuccessful() { public void sendSuccessful() {
if (pairingTimer != null) pairingTimer.cancel();
pairingTimer = new Timer(); pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() { pairingTimer.schedule(new TimerTask() {
@Override @Override
@@ -176,9 +183,10 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_timed_out)); cb.pairingFailed(context.getString(R.string.error_timed_out));
} }
Log.e("Device","Unpairing (timeout A)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
} }
}, 20*1000); }, 30*1000); //Time to wait for the other to accept
pairStatus = PairStatus.Requested; pairStatus = PairStatus.Requested;
} }
@@ -187,6 +195,7 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_could_not_send_package)); cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
} }
Log.e("Device","Unpairing (sendFailed A)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
} }
@@ -200,8 +209,7 @@ public class Device implements BaseLink.PackageReceiver {
public void unpair() { public void unpair() {
if (!isPaired()) return; //Log.e("Device","Unpairing (unpair)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
@@ -217,44 +225,70 @@ public class Device implements BaseLink.PackageReceiver {
} }
public void acceptPairing() { private void pairingDone() {
Log.i("Device","Accepted pairing"); //Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
//Send our own public key if (pairingTimer != null) pairingTimer.cancel();
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np); //TODO: Set a callback
pairStatus = PairStatus.Paired; pairStatus = PairStatus.Paired;
//Store as trusted device //Store as trusted device
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).commit(); preferences.edit().putBoolean(deviceId,true).commit();
//Store device information needed to create a Device object in a future //Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName()); editor.putString("deviceName", getName());
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey); editor.putString("publicKey", encodedPublicKey);
editor.commit(); editor.commit();
reloadPluginsFromSettings(); reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful(); for (PairingCallback cb : pairingCallback) {
cb.pairingSuccessful();
}
}
public void acceptPairing() {
Log.i("Device","Accepted pair request started by the other device");
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageFinishedCallback() {
@Override
public void sendSuccessful() {
pairingDone();
}
@Override
public void sendFailed() {
Log.e("Device","Unpairing (sendFailed B)");
pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_not_reachable));
}
}
});
} }
public void rejectPairing() { public void rejectPairing() {
Log.i("Device","Rejected pairing"); Log.i("Device","Rejected pair request started by the other device");
//Log.e("Device","Unpairing (rejectPairing)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", false); np.set("pair", false);
sendPackage(np); sendPackage(np);
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_user)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
}
} }
@@ -287,12 +321,14 @@ public class Device implements BaseLink.PackageReceiver {
Log.i("Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size()); Log.i("Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size());
/*
Collections.sort(links, new Comparator<BaseLink>() { Collections.sort(links, new Comparator<BaseLink>() {
@Override @Override
public int compare(BaseLink o, BaseLink o2) { public int compare(BaseLink o, BaseLink o2) {
return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority(); return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority();
} }
}); });
*/
link.addPackageReceiver(this); link.addPackageReceiver(this);
@@ -321,9 +357,12 @@ public class Device implements BaseLink.PackageReceiver {
if (wantsPair == isPaired()) { if (wantsPair == isPaired()) {
if (pairStatus == PairStatus.Requested) { if (pairStatus == PairStatus.Requested) {
//Log.e("Device","Unpairing (pair rejected)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
pairingTimer.cancel(); if (pairingTimer != null) pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
} }
return; return;
} }
@@ -338,7 +377,9 @@ public class Device implements BaseLink.PackageReceiver {
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("Device","Pairing exception: Received incorrect key"); Log.e("Device","Pairing exception: Received incorrect key");
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_invalid_key)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_invalid_key));
}
return; return;
} }
@@ -346,23 +387,9 @@ public class Device implements BaseLink.PackageReceiver {
Log.i("Pairing","Pair answer"); Log.i("Pairing","Pair answer");
pairStatus = PairStatus.Paired; if (pairingTimer != null) pairingTimer.cancel();
pairingTimer.cancel();
//Store as trusted device pairingDone();
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).commit();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName());
editor.putString("publicKey", encodedPublicKey);
editor.commit();
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
} else { } else {
@@ -381,7 +408,7 @@ public class Device implements BaseLink.PackageReceiver {
.setTicker(res.getString(R.string.pair_requested)) .setTicker(res.getString(R.string.pair_requested))
.setSmallIcon(android.R.drawable.ic_menu_help) .setSmallIcon(android.R.drawable.ic_menu_help)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND) .setDefaults(Notification.DEFAULT_ALL)
.build(); .build();
@@ -389,15 +416,17 @@ public class Device implements BaseLink.PackageReceiver {
notificationId = (int)System.currentTimeMillis(); notificationId = (int)System.currentTimeMillis();
notificationManager.notify(notificationId, noti); notificationManager.notify(notificationId, noti);
if (pairingTimer != null) pairingTimer.cancel();
pairingTimer = new Timer(); pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() { pairingTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
Log.e("Device","Unpairing (timeout B)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
notificationManager.cancel(notificationId); notificationManager.cancel(notificationId);
} }
}, 19*1000); //Time to show notification }, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
pairStatus = PairStatus.RequestedByPeer; pairStatus = PairStatus.RequestedByPeer;
for (PairingCallback cb : pairingCallback) cb.incomingRequest(); for (PairingCallback cb : pairingCallback) cb.incomingRequest();
@@ -407,26 +436,35 @@ public class Device implements BaseLink.PackageReceiver {
if (pairStatus == PairStatus.Requested) { if (pairStatus == PairStatus.Requested) {
pairingTimer.cancel(); pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
} else if (pairStatus == PairStatus.Paired) { } else if (pairStatus == PairStatus.Paired) {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).commit(); preferences.edit().remove(deviceId).commit();
reloadPluginsFromSettings(); reloadPluginsFromSettings();
} }
//Log.e("Device","Unpairing (unpair request)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) cb.unpaired(); for (PairingCallback cb : pairingCallback) cb.unpaired();
} }
} else if (!isPaired()) { } else if (!isPaired()) {
//TODO: Notify the other side that we don't trust them unpair();
Log.e("onPackageReceived","Device not paired, ignoring package!"); Log.e("onPackageReceived","Device not paired, ignoring package!");
} else { } else {
for (Plugin plugin : plugins.values()) { for (Plugin plugin : plugins.values()) {
plugin.onPackageReceived(np); try {
plugin.onPackageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived()");
}
} }
} }
@@ -441,33 +479,50 @@ public class Device implements BaseLink.PackageReceiver {
sendPackage(np,null); sendPackage(np,null);
} }
//Async
public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) { public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) {
final Exception backtrace = new Exception();
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
//Log.e("sendPackage", "Sending package...");
//Log.e("sendPackage", np.serialize());
boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()); boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
//Log.e("sendPackage", "Sending..."); //We need a copy to avoid concurrent modification exception if the original list changes
ArrayList<BaseLink> mLinks = new ArrayList<BaseLink>(links);
for(BaseLink link : links) { boolean success = false;
try {
boolean success; for (BaseLink link : mLinks) {
if (useEncryption) { if (useEncryption) {
success = link.sendPackageEncrypted(np, publicKey); success = link.sendPackageEncrypted(np, publicKey);
} else { } else {
success = link.sendPackage(np); success = link.sendPackage(np);
} }
if (success) { if (success) break;
//Log.e("sendPackage", "Sent");
if (callback != null) callback.sendSuccessful();
return;
} }
} catch(Exception e) {
e.printStackTrace();
Log.e("sendPackage","Error while sending package");
success = false;
} }
if (callback != null) callback.sendFailed(); if (success) {
Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)"); // Log.e("sendPackage","Package sent");
} else {
backtrace.printStackTrace();
Log.e("sendPackage","Error: Package could not be sent ("+mLinks.size()+" links available)");
}
if (callback != null) {
if (success) callback.sendSuccessful();
else callback.sendFailed();
}
} }
}).start(); }).start();
@@ -486,7 +541,7 @@ public class Device implements BaseLink.PackageReceiver {
return plugins.get(name); return plugins.get(name);
} }
private void addPlugin(final String name) { private synchronized void addPlugin(final String name) {
Plugin existing = plugins.get(name); Plugin existing = plugins.get(name);
if (existing != null) { if (existing != null) {
Log.w("addPlugin","plugin already present:" + name); Log.w("addPlugin","plugin already present:" + name);
@@ -504,24 +559,25 @@ public class Device implements BaseLink.PackageReceiver {
@Override @Override
public void run() { public void run() {
boolean success;
try { try {
boolean success = plugin.onCreate(); success = plugin.onCreate();
if (!success) {
Log.e("addPlugin", "plugin failed to load " + name);
failedPlugins.put(name, plugin);
return;
}
} catch (Exception e) { } catch (Exception e) {
failedPlugins.put(name, plugin); success = false;
e.printStackTrace(); e.printStackTrace();
Log.e("addPlugin", "Exception loading plugin " + name); Log.e("addPlugin", "Exception loading plugin " + name);
return; return;
} }
//Log.e("addPlugin","added " + name); if (success) {
//Log.e("addPlugin","added " + name);
failedPlugins.remove(name); failedPlugins.remove(name);
plugins.put(name, plugin); plugins.put(name, plugin);
} else {
Log.e("addPlugin", "plugin failed to load " + name);
plugins.remove(name);
failedPlugins.put(name, plugin);
}
for (PluginsChangedListener listener : pluginsChangedListeners) { for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(Device.this); listener.onPluginsChanged(Device.this);
@@ -532,13 +588,14 @@ public class Device implements BaseLink.PackageReceiver {
} }
private boolean removePlugin(String name) { private synchronized boolean removePlugin(String name) {
Plugin plugin = plugins.remove(name); Plugin plugin = plugins.remove(name);
Plugin failedPlugin = failedPlugins.remove(name); Plugin failedPlugin = failedPlugins.remove(name);
if (plugin == null) { if (plugin == null) {
if (failedPlugin == null) { if (failedPlugin == null) {
//Not found
return false; return false;
} }
plugin = failedPlugin; plugin = failedPlugin;
@@ -546,25 +603,22 @@ public class Device implements BaseLink.PackageReceiver {
try { try {
plugin.onDestroy(); plugin.onDestroy();
//Log.e("removePlugin","removed " + name);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("removePlugin","Exception calling onDestroy for plugin "+name); Log.e("removePlugin","Exception calling onDestroy for plugin "+name);
return false;
} }
//Log.e("removePlugin","removed " + name);
for (PluginsChangedListener listener : pluginsChangedListeners) { for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(this); listener.onPluginsChanged(this);
} }
return true; return true;
} }
public void setPluginEnabled(String pluginName, boolean value) { public void setPluginEnabled(String pluginName, boolean value) {
settings.edit().putBoolean(pluginName,value).commit(); settings.edit().putBoolean(pluginName,value).commit();
if (value) addPlugin(pluginName); if (value && isPaired() && isReachable()) addPlugin(pluginName);
else removePlugin(pluginName); else removePlugin(pluginName);
} }
@@ -574,6 +628,9 @@ public class Device implements BaseLink.PackageReceiver {
return enabled; return enabled;
} }
public boolean hasPluginsLoaded() {
return !plugins.isEmpty() || !failedPlugins.isEmpty();
}
public void reloadPluginsFromSettings() { public void reloadPluginsFromSettings() {
@@ -593,9 +650,7 @@ public class Device implements BaseLink.PackageReceiver {
} }
} }
for (PluginsChangedListener listener : pluginsChangedListeners) { //No need to call PluginsChangedListeners because addPlugin and removePlugin already do so
listener.onPluginsChanged(this);
}
} }
public HashMap<String,Plugin> getLoadedPlugins() { public HashMap<String,Plugin> getLoadedPlugins() {
@@ -610,7 +665,7 @@ public class Device implements BaseLink.PackageReceiver {
void onPluginsChanged(Device device); void onPluginsChanged(Device device);
} }
private ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>(); private final ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
public void addPluginsChangedListener(PluginsChangedListener listener) { public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener); pluginsChangedListeners.add(listener);

View File

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

View File

@@ -3,9 +3,7 @@ package org.kde.kdeconnect.Helpers;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.util.Log;
public class ContactsHelper { public class ContactsHelper {
@@ -31,14 +29,17 @@ public class ContactsHelper {
// Take the first match only // Take the first match only
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME); int nameIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
if (nameIndex != -1) { if (nameIndex != -1) {
String name = cursor.getString(nameIndex); String name = cursor.getString(nameIndex);
//Log.e("PhoneNumberLookup", "success: " + name); //Log.e("PhoneNumberLookup", "success: " + name);
return name; cursor.close();
return name + " (" + number + ")";
} }
} }
if (cursor != null) cursor.close();
return number; return number;
} }

View File

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

View File

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

View File

@@ -0,0 +1,101 @@
package org.kde.kdeconnect.Helpers;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;
//Code from http://stackoverflow.com/questions/9340332/how-can-i-get-the-list-of-mounted-external-storage-of-android-device/19982338#19982338
public class StorageHelper {
private static final String TAG = "StorageHelper";
public static class StorageInfo {
public final String path;
public final boolean readonly;
public final boolean removable;
public final int number;
StorageInfo(String path, boolean readonly, boolean removable, int number) {
this.path = path;
this.readonly = readonly;
this.removable = removable;
this.number = number;
}
}
public static List<StorageInfo> getStorageList() {
List<StorageInfo> list = new ArrayList<StorageInfo>();
String def_path = Environment.getExternalStorageDirectory().getPath();
boolean def_path_removable = Environment.isExternalStorageRemovable();
String def_path_state = Environment.getExternalStorageState();
boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
|| def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
HashSet<String> paths = new HashSet<String>();
int cur_removable_number = 1;
if (def_path_available) {
paths.add(def_path);
list.add(0, new StorageInfo(def_path, def_path_readonly, def_path_removable, def_path_removable ? cur_removable_number++ : -1));
}
BufferedReader buf_reader = null;
try {
buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
Log.d(TAG, "/proc/mounts");
while ((line = buf_reader.readLine()) != null) {
Log.d(TAG, line);
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
String unused = tokens.nextToken(); //device
String mount_point = tokens.nextToken(); //mount point
if (paths.contains(mount_point)) {
continue;
}
unused = tokens.nextToken(); //file system
List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
boolean readonly = flags.contains("ro");
if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure")
&& !line.contains("/mnt/asec")
&& !line.contains("/mnt/obb")
&& !line.contains("/dev/mapper")
&& !line.contains("tmpfs")) {
paths.add(mount_point);
list.add(new StorageInfo(mount_point, readonly, true, cur_removable_number++));
}
}
}
}
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (buf_reader != null) {
try {
buf_reader.close();
} catch (IOException ex) {}
}
}
return list;
}
}

View File

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

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.util.Log;
import android.widget.Button; import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
@@ -17,13 +16,11 @@ import org.kde.kdeconnect_tp.R;
public class BatteryPlugin extends Plugin { public class BatteryPlugin extends Plugin {
private NetworkPackage lastPackage = null; // keep these fields in sync with kdeconnect-kded:BatteryPlugin.h:ThresholdBatteryEvent
private static final int THRESHOLD_EVENT_NONE= 0;
private static final int THRESHOLD_EVENT_BATTERY_LOW = 1;
private IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); private NetworkPackage lastInfo = null;
/*static {
PluginFactory.registerPlugin(BatteryPlugin.class);
}*/
@Override @Override
public String getPluginName() { public String getPluginName() {
@@ -45,41 +42,46 @@ public class BatteryPlugin extends Plugin {
return context.getResources().getDrawable(R.drawable.icon); return context.getResources().getDrawable(R.drawable.icon);
} }
@Override
public boolean hasSettings() {
return false;
}
@Override @Override
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
return true; return true;
} }
private final BroadcastReceiver receiver = new BroadcastReceiver() {
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent batteryIntent) {
Log.i("BatteryPlugin", "Battery event"); Intent batteryChargeIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int level = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int currentCharge = level*100 / scale;
boolean isCharging = (0 != batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
boolean lowBattery = Intent.ACTION_BATTERY_LOW.equals(batteryIntent.getAction());
int thresholdEvent = lowBattery? THRESHOLD_EVENT_BATTERY_LOW : THRESHOLD_EVENT_NONE;
boolean isCharging = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)); if (lastInfo != null
&& isCharging != lastInfo.getBoolean("isCharging")
int currentCharge = 100; && currentCharge != lastInfo.getInt("currentCharge")
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); && thresholdEvent != lastInfo.getInt("thresholdEvent")
if (status != BatteryManager.BATTERY_STATUS_FULL) {
Intent batteryStatus = context.registerReceiver(null, filter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
currentCharge = level*100 / scale;
}
//Only notify if change is meaningful enough
if (lastPackage == null
|| (
isCharging != lastPackage.getBoolean("isCharging")
|| currentCharge != lastPackage.getInt("currentCharge")
)
) { ) {
//Do not send again if nothing has changed
return;
} else {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
np.set("isCharging", isCharging);
np.set("currentCharge", currentCharge); np.set("currentCharge", currentCharge);
np.set("isCharging", isCharging);
np.set("thresholdEvent", thresholdEvent);
device.sendPackage(np); device.sendPackage(np);
lastPackage = np; lastInfo = np;
} }
} }
@@ -87,12 +89,14 @@ public class BatteryPlugin extends Plugin {
@Override @Override
public boolean onCreate() { public boolean onCreate() {
context.registerReceiver(receiver, filter); context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_LOW));
return true; return true;
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
//It's okay to call this only once, even though we registered it for two filters
context.unregisterReceiver(receiver); context.unregisterReceiver(receiver);
} }
@@ -101,8 +105,8 @@ public class BatteryPlugin extends Plugin {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false; if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false;
if (np.getBoolean("request")) { if (np.getBoolean("request")) {
if (lastPackage != null) { if (lastInfo != null) {
device.sendPackage(lastPackage); device.sendPackage(lastInfo);
} }
} }
@@ -110,7 +114,7 @@ public class BatteryPlugin extends Plugin {
} }
@Override @Override
public AlertDialog getErrorDialog(Context baseContext) { public AlertDialog getErrorDialog(Activity deviceActivity) {
return null; return null;
} }

View File

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

View File

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

View File

@@ -0,0 +1,103 @@
package org.kde.kdeconnect.Plugins.MousePadPlugin;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import java.nio.ByteBuffer;
import java.util.HashMap;
public class KeyListenerView extends View {
private String deviceId;
private static HashMap<Integer, Integer> SpecialKeysMap = new HashMap<Integer, Integer>();
static {
int i = 0;
SpecialKeysMap.put(KeyEvent.KEYCODE_DEL, ++i); // 1
SpecialKeysMap.put(KeyEvent.KEYCODE_TAB, ++i); // 2
SpecialKeysMap.put(KeyEvent.KEYCODE_ENTER, 12); ++i; // 3 is not used, return is 12 instead
SpecialKeysMap.put(KeyEvent.KEYCODE_DPAD_LEFT, ++i); // 4
SpecialKeysMap.put(KeyEvent.KEYCODE_DPAD_UP, ++i); // 5
SpecialKeysMap.put(KeyEvent.KEYCODE_DPAD_RIGHT, ++i); // 6
SpecialKeysMap.put(KeyEvent.KEYCODE_DPAD_DOWN, ++i); // 7
SpecialKeysMap.put(KeyEvent.KEYCODE_PAGE_UP, ++i); // 8
SpecialKeysMap.put(KeyEvent.KEYCODE_PAGE_DOWN, ++i); // 9
if (Build.VERSION.SDK_INT >= 11) {
SpecialKeysMap.put(KeyEvent.KEYCODE_MOVE_HOME, ++i); // 10
SpecialKeysMap.put(KeyEvent.KEYCODE_MOVE_END, ++i); // 11
SpecialKeysMap.put(KeyEvent.KEYCODE_NUMPAD_ENTER, ++i); // 12
SpecialKeysMap.put(KeyEvent.KEYCODE_FORWARD_DEL, ++i); // 13
SpecialKeysMap.put(KeyEvent.KEYCODE_ESCAPE, ++i); // 14
}
}
public void setDeviceId(String id) {
deviceId = id;
}
public KeyListenerView(Context context, AttributeSet set) {
super(context, set);
setFocusable(true);
setFocusableInTouchMode(true);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
return null;
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
char utfChar = (char)event.getUnicodeChar();
if (utfChar == 9 || utfChar == 10) utfChar = 0; //Workaround to send enter and tab as special keys instead of characters
if (utfChar != 0) {
final String utfString = new String(new char[]{utfChar});
BackgroundService.RunCommand(getContext(), new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendKey(utfString);
}
});
} else {
if (!SpecialKeysMap.containsKey(keyCode)) {
return false;
}
final int specialKey = SpecialKeysMap.get(keyCode);
BackgroundService.RunCommand(getContext(), new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = (MousePadPlugin)device.getPlugin("plugin_mousepad");
if (mousePadPlugin == null) return;
mousePadPlugin.sendSpecialKey(specialKey);
}
});
}
return true;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,13 @@
package org.kde.kdeconnect.Plugins.MprisPlugin; package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.app.Activity; import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -24,9 +27,11 @@ import java.util.ArrayList;
public class MprisActivity extends Activity { public class MprisActivity extends Activity {
//TODO: Add a loading spinner at the begginning (to distinguish the loading state from a no-players state). //TODO: Add a loading spinner at the beginning (to distinguish the loading state from a no-players state).
//TODO 2: Add a message when no players are detected after loading completes //TODO 2: Add a message when no players are detected after loading completes
private String deviceId;
protected void connectToPlugin() { protected void connectToPlugin() {
final String deviceId = getIntent().getStringExtra("deviceId"); final String deviceId = getIntent().getStringExtra("deviceId");
@@ -67,8 +72,6 @@ public class MprisActivity extends Activity {
}); });
mpris.setPlayerListUpdatedHandler(new Handler() { mpris.setPlayerListUpdatedHandler(new Handler() {
boolean firstLoad = true;
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
final ArrayList<String> playerList = mpris.getPlayerList(); final ArrayList<String> playerList = mpris.getPlayerList();
@@ -93,24 +96,29 @@ public class MprisActivity extends Activity {
//Spotify doesn't support changing the volume yet... //Spotify doesn't support changing the volume yet...
if (player.equals("Spotify")) { if (player.equals("Spotify")) {
findViewById(R.id.volume_layout).setVisibility(View.INVISIBLE); findViewById(R.id.volume_layout).setVisibility(View.INVISIBLE);
findViewById(R.id.rew_button).setVisibility(View.GONE);
findViewById(R.id.ff_button).setVisibility(View.GONE);
} else { } else {
findViewById(R.id.volume_layout).setVisibility(View.VISIBLE); findViewById(R.id.volume_layout).setVisibility(View.VISIBLE);
findViewById(R.id.rew_button).setVisibility(View.VISIBLE);
findViewById(R.id.ff_button).setVisibility(View.VISIBLE);
} }
} }
@Override @Override
public void onNothingSelected(AdapterView<?> arg0) { public void onNothingSelected(AdapterView<?> arg0) {
mpris.setPlayer(null);
} }
}); });
// restore the selected player
int position = adapter.getPosition(mpris.getPlayer());
if (position >= 0) {
spinner.setSelection(position);
}
} }
}); });
if (firstLoad) {
firstLoad = false;
if (playerList.size() > 0) {
mpris.setPlayer(playerList.get(0));
}
}
} }
}); });
@@ -119,7 +127,7 @@ public class MprisActivity extends Activity {
} }
BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override @Override
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) { public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) {
connectToPlugin(); connectToPlugin();
@@ -142,12 +150,78 @@ public class MprisActivity extends Activity {
}); });
} }
/**
* Change current volume with provided step.
*
* @param mpris multimedia controller
* @param step step size volume change
*/
private void updateVolume(MprisPlugin mpris, int step) {
final int currentVolume = mpris.getVolume();
if(currentVolume < 100 || currentVolume > 0) {
int newVolume = currentVolume + step;
if(newVolume > 100) {
newVolume = 100;
} else if (newVolume <0 ) {
newVolume = 0;
}
mpris.setVolume(newVolume);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin) device.getPlugin("plugin_mpris");
if (mpris == null) return;
updateVolume(mpris, 5);
}
});
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin) device.getPlugin("plugin_mpris");
if (mpris == null) return;
updateVolume(mpris, -5);
}
});
return true;
default:
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
return true;
default:
return super.onKeyUp(keyCode, event);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.mpris_control); setContentView(R.layout.mpris_control);
final String deviceId = getIntent().getStringExtra("deviceId"); deviceId = getIntent().getStringExtra("deviceId");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String interval_time_str = prefs.getString(getString(R.string.mpris_time_key),
getString(R.string.mpris_time_default));
final int interval_time = Integer.parseInt(interval_time_str);
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() { BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override @Override
@@ -187,6 +261,36 @@ public class MprisActivity extends Activity {
} }
}); });
findViewById(R.id.rew_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(interval_time * -1);
}
});
}
});
findViewById(R.id.ff_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(interval_time);
}
});
}
});
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() { findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
@@ -204,10 +308,12 @@ public class MprisActivity extends Activity {
((SeekBar)findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { ((SeekBar)findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) { } public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) { } public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override @Override
public void onStopTrackingTouch(final SeekBar seekBar) { public void onStopTrackingTouch(final SeekBar seekBar) {
@@ -219,12 +325,14 @@ public class MprisActivity extends Activity {
if (mpris == null) return; if (mpris == null) return;
mpris.setVolume(seekBar.getProgress()); mpris.setVolume(seekBar.getProgress());
} }
}); });
} }
}); });
} }
} }

View File

@@ -30,10 +30,6 @@ public class MprisPlugin extends Plugin {
private String player = ""; private String player = "";
private boolean playing = false; private boolean playing = false;
/*static {
PluginFactory.registerPlugin(MprisPlugin.class);
}*/
@Override @Override
public String getPluginName() { public String getPluginName() {
return "plugin_mpris"; return "plugin_mpris";
@@ -54,6 +50,11 @@ public class MprisPlugin extends Plugin {
return context.getResources().getDrawable(R.drawable.icon); return context.getResources().getDrawable(R.drawable.icon);
} }
@Override
public boolean hasSettings() {
return true;
}
@Override @Override
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
return true; return true;
@@ -84,6 +85,13 @@ public class MprisPlugin extends Plugin {
device.sendPackage(np); device.sendPackage(np);
} }
public void Seek(int offset) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
np.set("player",player);
np.set("Seek",offset);
device.sendPackage(np);
}
@Override @Override
public boolean onPackageReceived(NetworkPackage np) { public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false; if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false;
@@ -172,6 +180,10 @@ public class MprisPlugin extends Plugin {
requestPlayerStatus(); requestPlayerStatus();
} }
public String getPlayer() {
return player;
}
public int getVolume() { public int getVolume() {
return volume; return volume;
} }
@@ -196,7 +208,7 @@ public class MprisPlugin extends Plugin {
} }
@Override @Override
public AlertDialog getErrorDialog(Context baseContext) { public AlertDialog getErrorDialog(Activity deviceActivity) {
return null; return null;
} }

View File

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

View File

@@ -6,31 +6,22 @@ import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.widget.Button; import android.widget.Button;
import org.kde.kdeconnect.Helpers.AppsHelper; import org.kde.kdeconnect.Helpers.AppsHelper;
import org.kde.kdeconnect.Helpers.ImagesHelper;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.DeviceActivity;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener { public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
/*static {
PluginFactory.registerPlugin(NotificationsPlugin.class);
}*/
@Override @Override
public String getPluginName() { public String getPluginName() {
return "plugin_notifications"; return "plugin_notifications";
@@ -51,6 +42,11 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
return context.getResources().getDrawable(R.drawable.icon); return context.getResources().getDrawable(R.drawable.icon);
} }
@Override
public boolean hasSettings() {
return false;
}
@Override @Override
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
return true; return true;
@@ -116,7 +112,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public boolean onCreate() { public boolean onCreate() {
if (Build.VERSION.SDK_INT < 18) return false; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
//Check for permissions //Check for permissions
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners"); String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
@@ -126,12 +124,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
public void onServiceStart(NotificationReceiver service) { public void onServiceStart(NotificationReceiver service) {
try { try {
service.addListener(NotificationsPlugin.this); service.addListener(NotificationsPlugin.this);
/*
StatusBarNotification[] notifications = service.getActiveNotifications(); StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) { for (StatusBarNotification notification : notifications) {
onNotificationPosted(notification); sendNotification(notification, true);
} }
*/
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("NotificationsPlugin","Exception"); Log.e("NotificationsPlugin","Exception");
@@ -148,6 +144,11 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public void onDestroy() { public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return;
}
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() { NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override @Override
public void onServiceStart(NotificationReceiver service) { public void onServiceStart(NotificationReceiver service) {
@@ -178,6 +179,13 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) { public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) {
Notification notification = statusBarNotification.getNotification(); Notification notification = statusBarNotification.getNotification();
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
|| (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0 ) {
//This is not a notification!
return;
}
NotificationId id = NotificationId.fromNotification(statusBarNotification); NotificationId id = NotificationId.fromNotification(statusBarNotification);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
@@ -204,7 +212,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
np.set("id", id.serialize()); np.set("id", id.serialize());
np.set("appName", appName == null? packageName : appName); np.set("appName", appName == null? packageName : appName);
np.set("isClearable", statusBarNotification.isClearable()); np.set("isClearable", statusBarNotification.isClearable());
np.set("ticker", notification.tickerText.toString()); np.set("ticker", getTickerText(notification));
np.set("time", Long.toString(statusBarNotification.getPostTime())); np.set("time", Long.toString(statusBarNotification.getPostTime()));
if (requestAnswer) np.set("requestAnswer", true); if (requestAnswer) np.set("requestAnswer", true);
@@ -212,7 +220,43 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
} }
/**
* Returns the ticker text of the notification.
* If device android version is KitKat or newer, the title and text of the notification is used
* instead the ticker text.
*/
private String getTickerText(Notification notification) {
final String TITLE_KEY = "android.title";
final String TEXT_KEY = "android.text";
String ticker = "";
if(notification != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
Bundle extras = notification.extras;
String extraTitle = extras.getString(TITLE_KEY);
String extraText = extras.getString(TEXT_KEY);
if (extraTitle != null && extraText != null) {
ticker = extraTitle + " " + extraText;
} else if (extraTitle != null) {
ticker = extraTitle;
} else if (extraText != null) {
ticker = extraText;
}
} catch(Exception e) {
Log.w("NotificationPlugin","problem parsing notification extras");
e.printStackTrace();
}
}
if (ticker.isEmpty()) {
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
}
}
return ticker;
}
@Override @Override
@@ -276,10 +320,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public AlertDialog getErrorDialog(final Context baseContext) { public AlertDialog getErrorDialog(final Activity deviceActivity) {
if (Build.VERSION.SDK_INT < 18) { if (Build.VERSION.SDK_INT < 18) {
return new AlertDialog.Builder(baseContext) return new AlertDialog.Builder(deviceActivity)
.setTitle(R.string.pref_plugin_notifications) .setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.plugin_not_available) .setMessage(R.string.plugin_not_available)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@@ -290,14 +334,14 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}) })
.create(); .create();
} else { } else {
return new AlertDialog.Builder(baseContext) return new AlertDialog.Builder(deviceActivity)
.setTitle(R.string.pref_plugin_notifications) .setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.no_permissions) .setMessage(R.string.no_permissions)
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
baseContext.startActivity(intent); deviceActivity.startActivityForResult(intent, DeviceActivity.RESULT_NEEDS_RELOAD);
} }
}) })
.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {

View File

@@ -4,23 +4,23 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
public class PingPlugin extends Plugin { public class PingPlugin extends Plugin {
/*static {
PluginFactory.registerPlugin(PingPlugin.class);
}*/
@Override @Override
public String getPluginName() { public String getPluginName() {
return "plugin_ping"; return "plugin_ping";
@@ -41,6 +41,11 @@ public class PingPlugin extends Plugin {
return context.getResources().getDrawable(R.drawable.icon); return context.getResources().getDrawable(R.drawable.icon);
} }
@Override
public boolean hasSettings() {
return false;
}
@Override @Override
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
return true; return true;
@@ -63,17 +68,36 @@ public class PingPlugin extends Plugin {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) { if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
//Log.e("PingPackageReceiver", "was a ping!"); //Log.e("PingPackageReceiver", "was a ping!");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(new Intent(context, MainActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
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) Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName()) .setContentTitle(device.getName())
.setContentText("Ping!") .setContentText(message)
.setTicker("Ping!") .setContentIntent(resultPendingIntent)
.setTicker(message)
.setSmallIcon(android.R.drawable.ic_dialog_alert) .setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND) .setDefaults(Notification.DEFAULT_ALL)
.build(); .build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(42 /*a unique id to create only one notification*/, noti); notificationManager.notify(id, noti);
return true; return true;
} }
@@ -81,7 +105,7 @@ public class PingPlugin extends Plugin {
} }
@Override @Override
public AlertDialog getErrorDialog(Context baseContext) { public AlertDialog getErrorDialog(Activity deviceActivity) {
return null; return null;
} }

View File

@@ -51,6 +51,12 @@ public abstract class Plugin {
*/ */
public abstract boolean isEnabledByDefault(); public abstract boolean isEnabledByDefault();
/**
* Return true if this plugin needs an specific UI settings.
*/
public abstract boolean hasSettings();
/** /**
* Initialize the listeners and structures in your plugin. * Initialize the listeners and structures in your plugin.
* Should return true if initialization was successful. * Should return true if initialization was successful.
@@ -73,7 +79,7 @@ public abstract class Plugin {
* If onCreate returns false, should create a dialog explaining * If onCreate returns false, should create a dialog explaining
* the problem (and how to fix it, if possible) to the user. * the problem (and how to fix it, if possible) to the user.
*/ */
public abstract AlertDialog getErrorDialog(Context baseContext); public abstract AlertDialog getErrorDialog(Activity deviceActivity);
/** /**
* Creates a button that will be displayed in the user interface * Creates a button that will be displayed in the user interface

View File

@@ -7,10 +7,13 @@ import android.util.Log;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin; import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin; import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin; import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin; import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin; import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin; import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
import java.util.Map; import java.util.Map;
@@ -21,12 +24,14 @@ public class PluginFactory {
public static class PluginInfo { public static class PluginInfo {
public PluginInfo(String pluginName, String displayName, String description, Drawable icon, boolean enabledByDefault) { public PluginInfo(String pluginName, String displayName, String description, Drawable icon,
boolean enabledByDefault, boolean hasSettings) {
this.pluginName = pluginName; this.pluginName = pluginName;
this.displayName = displayName; this.displayName = displayName;
this.description = description; this.description = description;
this.icon = icon; this.icon = icon;
this.enabledByDefault = enabledByDefault; this.enabledByDefault = enabledByDefault;
this.hasSettings = hasSettings;
} }
public String getPluginName() { public String getPluginName() {
@@ -45,15 +50,18 @@ public class PluginFactory {
return icon; return icon;
} }
public boolean hasSettings() { return hasSettings; }
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
return enabledByDefault; return enabledByDefault;
} }
private String pluginName; private final String pluginName;
private String displayName; private final String displayName;
private String description; private final String description;
private final Drawable icon; private final Drawable icon;
private boolean enabledByDefault; private final boolean enabledByDefault;
private final boolean hasSettings;
} }
@@ -61,13 +69,16 @@ public class PluginFactory {
private static final Map<String, PluginInfo> availablePluginsInfo = new TreeMap<String, PluginInfo>(); private static final Map<String, PluginInfo> availablePluginsInfo = new TreeMap<String, PluginInfo>();
static { static {
//TODO: Avoid this factory having to know every plugin //TODO: Use reflection to find all subclasses of Plugin, instead of adding them manually
PluginFactory.registerPlugin(TelephonyPlugin.class); PluginFactory.registerPlugin(TelephonyPlugin.class);
PluginFactory.registerPlugin(PingPlugin.class); PluginFactory.registerPlugin(PingPlugin.class);
PluginFactory.registerPlugin(MprisPlugin.class); PluginFactory.registerPlugin(MprisPlugin.class);
PluginFactory.registerPlugin(ClipboardPlugin.class); PluginFactory.registerPlugin(ClipboardPlugin.class);
PluginFactory.registerPlugin(BatteryPlugin.class); PluginFactory.registerPlugin(BatteryPlugin.class);
PluginFactory.registerPlugin(SftpPlugin.class);
PluginFactory.registerPlugin(NotificationsPlugin.class); PluginFactory.registerPlugin(NotificationsPlugin.class);
PluginFactory.registerPlugin(MousePadPlugin.class);
PluginFactory.registerPlugin(SharePlugin.class);
} }
public static PluginInfo getPluginInfo(Context context, String pluginName) { public static PluginInfo getPluginInfo(Context context, String pluginName) {
@@ -76,7 +87,8 @@ public class PluginFactory {
try { try {
Plugin p = ((Plugin)availablePlugins.get(pluginName).newInstance()); Plugin p = ((Plugin)availablePlugins.get(pluginName).newInstance());
p.setContext(context, null); p.setContext(context, null);
info = new PluginInfo(pluginName, p.getDisplayName(), p.getDescription(), p.getIcon(), p.isEnabledByDefault()); info = new PluginInfo(pluginName, p.getDisplayName(), p.getDescription(), p.getIcon(),
p.isEnabledByDefault(), p.hasSettings());
availablePluginsInfo.put(pluginName, info); //Cache it availablePluginsInfo.put(pluginName, info); //Cache it
return info; return info;
} catch(Exception e) { } catch(Exception e) {
@@ -109,10 +121,10 @@ public class PluginFactory {
} }
public static void registerPlugin(Class pluginClass) { public static void registerPlugin(Class<? extends Plugin> pluginClass) {
try { try {
//I hate this but I need to create an instance because abstract static functions can't be declared //I hate this but I need to create an instance because abstract static functions can't be declared
String pluginName = ((Plugin)pluginClass.newInstance()).getPluginName(); String pluginName = (pluginClass.newInstance()).getPluginName();
availablePlugins.put(pluginName, pluginClass); availablePlugins.put(pluginName, pluginClass);
} catch(Exception e) { } catch(Exception e) {
Log.e("PluginFactory","addPlugin exception"); Log.e("PluginFactory","addPlugin exception");

View File

@@ -0,0 +1,210 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;
import android.content.Context;
import android.util.Log;
import org.apache.http.conn.util.InetAddressUtils;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.filesystem.NativeFileSystemView;
import org.apache.sshd.server.filesystem.NativeSshFile;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.kde.kdeconnect.Device;
import java.io.File;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
class SimplePasswordAuthenticator implements PasswordAuthenticator {
public void setUser(String user) {this.user = user;}
public String getUser() {return this.user;}
public void setPassword(String password) {this.password = password;}
public String getPassword() {return this.password;}
@Override
public boolean authenticate(String user, String password, ServerSession session) {
return user.equals(this.user) && password.equals(this.password);
}
private String user;
private String password;
}
class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
private List<PublicKey> keys = new ArrayList<PublicKey>();
public void addKey(PublicKey key) {
keys.add(key);
}
@Override
public boolean authenticate(String user, PublicKey key, ServerSession session) {
for (PublicKey k : keys) {
if (key.equals(k)) {
return true;
}
}
return false;
}
}
class SimpleSftpServer {
private static final int STARTPORT = 1739;
private static final int ENDPORT = 1764;
private static final String USER = "kdeconnect";
public static int port = -1;
private static boolean started = false;
public final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
public final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
private final SshServer sshd = SshServer.setUpDefaultServer();
public void init(Context ctx, Device device) {
passwordAuth.setUser(USER);
keyAuth.addKey(device.publicKey);
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(ctx.getFilesDir() + "/sftpd.ser"));
//sshd.setFileSystemFactory(new NativeFileSystemFactory());
sshd.setFileSystemFactory(new SecureFileSystemFactory());
//sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
sshd.setPasswordAuthenticator(passwordAuth);
sshd.setPublickeyAuthenticator(keyAuth);
}
public boolean start() {
if (!started) {
String password = Long.toHexString(Double.doubleToLongBits(Math.random()));
passwordAuth.setPassword(password);
port = STARTPORT;
while(!started) {
try {
sshd.setPort(port);
sshd.start();
started = true;
} catch(Exception e) {
port++;
if (port >= ENDPORT) {
port = -1;
Log.e("SftpServer", "No more ports available");
return false;
}
}
}
}
return true;
}
public void stop() {
try {
started = false;
sshd.stop();
} catch (InterruptedException e) {
}
}
public String getLocalIpAddress() {
String ip6 = null;
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
String address = inetAddress.getHostAddress();
if (InetAddressUtils.isIPv4Address(address)) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
return address;
} else {
ip6 = address;
}
}
}
}
} catch (SocketException ex) {
}
return ip6;
}
}
class SecureFileSystemFactory implements FileSystemFactory {
public SecureFileSystemFactory() {}
@Override
public FileSystemView createFileSystemView(final Session username) {
final String base = "/";
return new SecureFileSystemView(base, username.getUsername());
}
}
class SecureFileSystemView extends NativeFileSystemView {
// the first and the last character will always be '/'
// It is always with respect to the root directory.
private String currDir = "/";
private String rootDir = "/";
private String userName;
private boolean caseInsensitive = false;
//
public SecureFileSystemView(final String rootDir, final String userName) {
super(userName);
this.rootDir = NativeSshFile.normalizeSeparateChar(rootDir);
this.userName = userName;
}
//
@Override
public SshFile getFile(final String file) {
return getFile(currDir, file);
}
@Override
public SshFile getFile(final SshFile baseDir, final String file) {
return getFile(baseDir.getAbsolutePath(), file);
}
//
protected SshFile getFile(final String dir, final String file) {
// get actual file object
String physicalName = NativeSshFile.getPhysicalName("/", dir, file, caseInsensitive);
File fileObj = new File(rootDir, physicalName); // chroot
// strip the root directory and return
String userFileName = physicalName.substring("/".length() - 1);
return new SecureSshFile(this, userFileName, fileObj, userName);
}
}
class SecureSshFile extends NativeSshFile {
//
public SecureSshFile(final SecureFileSystemView view, final String fileName, final File file, final String userName) {
super(fileName, file, userName);
}
}

View File

@@ -0,0 +1,128 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.widget.Button;
import org.kde.kdeconnect.Helpers.StorageHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class SftpPlugin extends Plugin {
private static final SimpleSftpServer server = new SimpleSftpServer();
@Override
public String getPluginName() {return "plugin_sftp";}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sftp);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_sftp_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean hasSettings() {
return false;
}
@Override
public boolean isEnabledByDefault() { return true; }
@Override
public boolean onCreate() {
server.init(context, device);
return true;
}
@Override
public void onDestroy() {
server.stop();
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SFTP)) return false;
if (np.getBoolean("startBrowsing")) {
if (server.start()) {
NetworkPackage np2 = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SFTP);
np2.set("ip", server.getLocalIpAddress());
np2.set("port", server.port);
np2.set("user", server.passwordAuth.getUser());
np2.set("password", server.passwordAuth.getPassword());
//Kept for compatibility, but new desktop clients will read "multiPaths" instead,
// that supports devices with more than one external storage
np2.set("path", Environment.getExternalStorageDirectory().getAbsolutePath());
List<StorageHelper.StorageInfo> storageList = StorageHelper.getStorageList();
ArrayList<String> paths = new ArrayList<String>();
ArrayList<String> pathNames = new ArrayList<String>();
for (StorageHelper.StorageInfo storage : storageList) {
paths.add(storage.path);
StringBuilder res = new StringBuilder();
if (storageList.size() > 1) {
if (!storage.removable) {
res.append(context.getString(R.string.sftp_internal_storage));
} else if (storage.number > 1) {
res.append(context.getString(R.string.sftp_sdcard_num, storage.number));
} else {
res.append(context.getString(R.string.sftp_sdcard));
}
} else {
res.append(context.getString(R.string.sftp_all_files));
}
if (storage.readonly) {
res.append(" ");
res.append(context.getString(R.string.sftp_readonly));
}
pathNames.add(res.toString());
}
//Shortcut for users that only want to browse camera pictures
String cameraDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera";
if (new File(cameraDir).exists()) {
paths.add(cameraDir);
pathNames.add(context.getString(R.string.sftp_camera));
}
np2.set("multiPaths", paths);
np2.set("pathNames", pathNames);
device.sendPackage(np2);
return true;
}
}
return false;
}
@Override
public AlertDialog getErrorDialog(Activity deviceActivity) { return null; }
@Override
public Button getInterfaceButton(Activity activity) { return null; }
}

View File

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

View File

@@ -10,7 +10,6 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Button; import android.widget.Button;
import org.kde.kdeconnect.Helpers.ContactsHelper; import org.kde.kdeconnect.Helpers.ContactsHelper;
@@ -20,10 +19,6 @@ import org.kde.kdeconnect_tp.R;
public class TelephonyPlugin extends Plugin { public class TelephonyPlugin extends Plugin {
/*static {
PluginFactory.registerPlugin(TelephonyPlugin.class);
}*/
@Override @Override
public String getPluginName() { public String getPluginName() {
return "plugin_telephony"; return "plugin_telephony";
@@ -49,7 +44,12 @@ public class TelephonyPlugin extends Plugin {
return true; return true;
} }
private BroadcastReceiver receiver = new BroadcastReceiver() { @Override
public boolean hasSettings() {
return false;
}
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -96,6 +96,8 @@ public class TelephonyPlugin extends Plugin {
public void callBroadcastReceived(int state, String phoneNumber) { public void callBroadcastReceived(int state, String phoneNumber) {
//Log.e("TelephonyPlugin", "callBroadcastReceived");
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY);
if (phoneNumber != null) { if (phoneNumber != null) {
phoneNumber = ContactsHelper.phoneNumberLookup(context,phoneNumber); phoneNumber = ContactsHelper.phoneNumberLookup(context,phoneNumber);
@@ -162,6 +164,7 @@ public class TelephonyPlugin extends Plugin {
@Override @Override
public boolean onCreate() { public boolean onCreate() {
//Log.e("TelephonyPlugin", "onCreate");
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
context.registerReceiver(receiver, filter); context.registerReceiver(receiver, filter);
@@ -180,7 +183,7 @@ public class TelephonyPlugin extends Plugin {
} }
@Override @Override
public AlertDialog getErrorDialog(Context baseContext) { public AlertDialog getErrorDialog(Activity deviceActivity) {
return null; return null;
} }

View File

@@ -0,0 +1,201 @@
package org.kde.kdeconnect.UserInterface;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.List.ButtonItem;
import org.kde.kdeconnect.UserInterface.List.CustomItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect.UserInterface.List.SmallEntryItem;
import org.kde.kdeconnect.UserInterface.List.TextItem;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
public class DeviceActivity extends ActionBarActivity {
static private String deviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
private Device device;
public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER;
TextView errorHeader;
private final Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
@Override
public void onPluginsChanged(final Device device) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
if (!device.isReachable()) {
//Not reachable, show unpair button
Button b = new Button(DeviceActivity.this);
b.setText(R.string.device_menu_unpair);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
device.unpair();
finish();
}
});
items.add(new TextItem(getString(R.string.device_not_reachable)));
items.add(new ButtonItem(b));
} else {
//Plugins button list
final Collection<Plugin> plugins = device.getLoadedPlugins().values();
for (Plugin p : plugins) {
Button b = p.getInterfaceButton(DeviceActivity.this);
if (b != null) {
items.add(new SectionItem(p.getDisplayName()));
items.add(new ButtonItem(b));
}
}
//Failed plugins List
final Collection<Plugin> failed = device.getFailedPlugins().values();
if (!failed.isEmpty()) {
if (errorHeader == null) {
errorHeader = new TextView(DeviceActivity.this);
errorHeader.setPadding(0, 48, 0, 0);
errorHeader.setOnClickListener(null);
errorHeader.setOnLongClickListener(null);
errorHeader.setText(getResources().getString(R.string.plugins_failed_to_load));
}
items.add(new CustomItem(errorHeader));
for (final Plugin p : failed) {
items.add(new SmallEntryItem(p.getDisplayName(), new View.OnClickListener() {
@Override
public void onClick(View v) {
p.getErrorDialog(DeviceActivity.this).show();
}
}));
}
}
}
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
ListAdapter adapter = new ListAdapter(DeviceActivity.this, items);
buttonsList.setAdapter(adapter);
} catch(ConcurrentModificationException e) {
Log.e("DeviceActivity", "ConcurrentModificationException");
this.run(); //Try again
}
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setDisplayHomeAsUpEnabled(true);
if (getIntent().hasExtra("deviceId")) {
deviceId = getIntent().getStringExtra("deviceId");
}
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
if (device == null) return;
setTitle(device.getName());
device.addPluginsChangedListener(pluginsChangedListener);
pluginsChangedListener.onPluginsChanged(device);
if (!device.hasPluginsLoaded()) {
device.reloadPluginsFromSettings();
}
}
});
}
@Override
protected void onDestroy() {
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
if (device == null) return;
device.removePluginsChangedListener(pluginsChangedListener);
}
});
super.onDestroy();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
if (device != null && device.isPaired()) {
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
intent.putExtra("deviceId", deviceId);
startActivity(intent);
return true;
}
});
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
device.unpair();
finish();
return true;
}
});
return true;
} else {
return false;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode)
{
case RESULT_NEEDS_RELOAD:
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
device.reloadPluginsFromSettings();
}
});
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -0,0 +1,20 @@
package org.kde.kdeconnect.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
public class CustomItem implements ListAdapter.Item {
private final View view;
public CustomItem(View v) {
this.view = v;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
return view;
}
}

View File

@@ -4,6 +4,7 @@ package org.kde.kdeconnect.UserInterface.List;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@@ -24,7 +25,10 @@ public class DeviceItem implements ListAdapter.Item {
@Override @Override
public View inflateView(LayoutInflater layoutInflater) { public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(R.layout.list_item_entry, null); final View v = layoutInflater.inflate(R.layout.list_item_entry, null);
//Highlight when selected effect
v.setBackgroundDrawable(layoutInflater.getContext().getResources().getDrawable(R.drawable.kitkatcompatselector_list_selector_holo_dark));
TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title); TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
if (titleView != null) titleView.setText(device.getName()); if (titleView != null) titleView.setText(device.getName());

View File

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

View File

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

View File

@@ -9,11 +9,11 @@ import org.kde.kdeconnect_tp.R;
public class SectionItem implements ListAdapter.Item { public class SectionItem implements ListAdapter.Item {
private final String title; private final String title;
public boolean isEmpty; public boolean isSectionEmpty;
public SectionItem(String title) { public SectionItem(String title) {
this.title = title; this.title = title;
this.isEmpty = false; this.isSectionEmpty = false;
} }
@Override @Override
@@ -21,14 +21,14 @@ public class SectionItem implements ListAdapter.Item {
View v = layoutInflater.inflate(R.layout.list_item_category, null); View v = layoutInflater.inflate(R.layout.list_item_category, null);
//Make it not selectable
v.setOnClickListener(null); v.setOnClickListener(null);
v.setOnLongClickListener(null); v.setOnLongClickListener(null);
v.setLongClickable(false);
TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text); TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text);
sectionView.setText(title); sectionView.setText(title);
if (isEmpty) { if (isSectionEmpty) {
v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE); v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE);
} }

View File

@@ -0,0 +1,40 @@
package org.kde.kdeconnect.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.kde.kdeconnect_tp.R;
public class SmallEntryItem implements ListAdapter.Item {
private final String title;
private final View.OnClickListener clickListener;
public SmallEntryItem(String title) {
this.title = title;
this.clickListener = null;
}
public SmallEntryItem(String title, View.OnClickListener clickListener) {
this.title = title;
this.clickListener = clickListener;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(android.R.layout.simple_list_item_1, null);
TextView titleView = (TextView)v.findViewById(android.R.id.text1);
if (titleView != null) titleView.setText(title);
if (clickListener != null) {
titleView.setOnClickListener(clickListener);
v.setBackgroundDrawable(layoutInflater.getContext().getResources().getDrawable(R.drawable.kitkatcompatselector_list_selector_holo_dark));
}
return v;
}
}

View File

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

View File

@@ -1,11 +1,11 @@
package org.kde.kdeconnect.UserInterface; package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@@ -66,6 +66,9 @@ public class MainActivity extends ActionBarActivity {
} }
}).start(); }).start();
break; break;
case R.id.menu_settings:
startActivity(new Intent(this,MainSettingsActivity.class));
break;
default: default:
break; break;
} }
@@ -79,7 +82,6 @@ public class MainActivity extends ActionBarActivity {
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
} }
@@ -102,36 +104,36 @@ public class MainActivity extends ActionBarActivity {
Resources res = getResources(); Resources res = getResources();
section = new SectionItem(res.getString(R.string.category_connected_devices)); section = new SectionItem(res.getString(R.string.category_connected_devices));
section.isEmpty = true; section.isSectionEmpty = true;
items.add(section); items.add(section);
for(Device d : devices) { for(Device d : devices) {
if (d.isReachable() && d.isPaired()) { if (d.isReachable() && d.isPaired()) {
items.add(new DeviceItem(MainActivity.this, d)); items.add(new DeviceItem(MainActivity.this, d));
section.isEmpty = false; section.isSectionEmpty = false;
} }
} }
section = new SectionItem(res.getString(R.string.category_not_paired_devices)); section = new SectionItem(res.getString(R.string.category_not_paired_devices));
section.isEmpty = true; section.isSectionEmpty = true;
items.add(section); items.add(section);
for(Device d : devices) { for(Device d : devices) {
if (d.isReachable() && !d.isPaired()) { if (d.isReachable() && !d.isPaired()) {
items.add(new DeviceItem(MainActivity.this, d)); items.add(new DeviceItem(MainActivity.this, d));
section.isEmpty = false; section.isSectionEmpty = false;
} }
} }
section = new SectionItem(res.getString(R.string.category_remembered_devices)); section = new SectionItem(res.getString(R.string.category_remembered_devices));
section.isEmpty = true; section.isSectionEmpty = true;
items.add(section); items.add(section);
for(Device d : devices) { for(Device d : devices) {
if (!d.isReachable() && d.isPaired()) { if (!d.isReachable() && d.isPaired()) {
items.add(new DeviceItem(MainActivity.this, d)); items.add(new DeviceItem(MainActivity.this, d));
section.isEmpty = false; section.isSectionEmpty = false;
} }
} }
if (section.isEmpty) { if (section.isSectionEmpty) {
items.remove(items.size()-1); //Remove section items.remove(items.size()-1); //Remove remembered devices section if empty
} }
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -142,12 +144,13 @@ public class MainActivity extends ActionBarActivity {
list.setOnItemClickListener(new AdapterView.OnItemClickListener() { list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
view.callOnClick(); view.performClick();
} }
}); });
} }
}); });
} }
}); });
} }

View File

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

View File

@@ -0,0 +1,35 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import org.kde.kdeconnect_tp.R;
public class MaxWidthImageButton extends ImageButton {
int mMaxWidth = Integer.MAX_VALUE;
public MaxWidthImageButton(Context context) {
super(context);
}
public MaxWidthImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaxWidthImageButton);
mMaxWidth = a.getDimensionPixelSize(R.styleable.MaxWidthImageButton_maxWidth, Integer.MAX_VALUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(getMeasuredWidth() > mMaxWidth){
setMeasuredDimension(mMaxWidth, getMeasuredHeight());
}
}
}

View File

@@ -18,7 +18,7 @@ public class PairActivity extends ActionBarActivity {
private String deviceId; private String deviceId;
private Device device = null; private Device device = null;
private Device.PairingCallback pairingCallback = new Device.PairingCallback() { private final Device.PairingCallback pairingCallback = new Device.PairingCallback() {
@Override @Override
public void incomingRequest() { public void incomingRequest() {
@@ -76,6 +76,7 @@ public class PairActivity extends ActionBarActivity {
@Override @Override
public void onServiceStart(BackgroundService service) { public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId); device = service.getDevice(deviceId);
if (device == null) return;
setTitle(device.getName()); setTitle(device.getName());
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(device.getNotificationId()); notificationManager.cancel(device.getNotificationId());
@@ -94,6 +95,7 @@ public class PairActivity extends ActionBarActivity {
@Override @Override
public void onServiceStart(BackgroundService service) { public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId); device = service.getDevice(deviceId);
if (device == null) return;
device.requestPairing(); device.requestPairing();
} }
}); });
@@ -134,6 +136,8 @@ public class PairActivity extends ActionBarActivity {
BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() { BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
@Override @Override
public void onServiceStart(BackgroundService service) { public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
if (device == null) return;
device.addPairingCallback(pairingCallback); device.addPairingCallback(pairingCallback);
} }
}); });

View File

@@ -0,0 +1,17 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class PluginSettingsActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String resource_name = getIntent().getStringExtra(Intent.EXTRA_INTENT);
int resource_file = getResources().getIdentifier(resource_name, "xml", getPackageName());
addPreferencesFromResource(resource_file);
}
}

View File

@@ -0,0 +1,88 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Intent;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.view.View;
import android.widget.AdapterView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Set;
public class SettingsActivity extends PreferenceActivity {
static private String deviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(this);
setPreferenceScreen(preferenceScreen);
if (getIntent().hasExtra("deviceId")) {
deviceId = getIntent().getStringExtra("deviceId");
}
BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
final Device device = service.getDevice(deviceId);
Set<String> plugins = PluginFactory.getAvailablePlugins();
final ArrayList<Preference> preferences = new ArrayList<Preference>();
for (final String pluginName : plugins) {
final CheckBoxPreference pref = new CheckBoxPreference(getBaseContext());
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(getBaseContext(), pluginName);
pref.setKey(pluginName);
pref.setTitle(info.getDisplayName());
pref.setSummary(info.getDescription());
pref.setChecked(device.isPluginEnabled(pluginName));
preferences.add(pref);
preferenceScreen.addPreference(pref);
if (info.hasSettings()) {
final Preference pluginPreference = new Preference(getBaseContext());
pluginPreference.setKey(pluginName + getString(R.string.plugin_settings_key));
pluginPreference.setTitle(info.getDisplayName());
pluginPreference.setSummary(R.string.plugin_settings);
preferences.add(pluginPreference);
preferenceScreen.addPreference(pluginPreference);
pluginPreference.setDependency(pref.getKey());
}
}
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Preference pref = preferences.get(i);
if (pref.getDependency() == null) { //Is a plugin check
CheckBoxPreference check = (CheckBoxPreference)pref;
boolean enabled = device.isPluginEnabled(pref.getKey());
device.setPluginEnabled(pref.getKey(), !enabled);
check.setChecked(!enabled);
} else { //Is a plugin suboption
if (pref.isEnabled()) {
Intent intent = new Intent(SettingsActivity.this, PluginSettingsActivity.class);
intent.putExtra(Intent.EXTRA_INTENT, pref.getKey());
startActivity(intent);
}
}
}
});
}
});
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

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