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

Compare commits

..

1 Commits
v0.6.4 ... v0.1

Author SHA1 Message Date
Albert Vaca
4b82a2ef3f Commented BatteryPlugin for release
It needs a patch in kdelibs to work.
2013-09-10 11:59:43 +02:00
115 changed files with 704 additions and 4632 deletions

9
.gitignore vendored
View File

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

339
COPYING
View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

1
KdeConnect/.gitignore vendored Normal file
View File

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

79
KdeConnect/KdeConnect.iml Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.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="ASSEMBLE_TEST_TASK_NAME" value="assembleTest" />
<option name="SOURCE_GEN_TASK_NAME" value="TODO" />
<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/exploded-bundles" />
<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="mina-core-2.0.7" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.6.6" level="project" />
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
<orderEntry type="library" exported="" name="ComAndroidSupportAppcompatV71800.aar" level="project" />
</component>
</module>

26
KdeConnect/build.gradle Normal file
View File

@@ -0,0 +1,26 @@
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.

Binary file not shown.

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="20" android:versionCode="4"
android:versionName="0.6.4"> android:versionName="0.2.1">
<uses-sdk android:minSdkVersion="9" <uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="19" /> android:targetSdkVersion="18" />
<supports-screens <supports-screens
android:smallScreens="true" android:smallScreens="true"
@@ -18,15 +18,13 @@
<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"
@@ -34,11 +32,6 @@
android:label="KDE Connect" android:label="KDE Connect"
> >
<service
android:enabled="true"
android:name="org.kde.kdeconnect.BackgroundService">
</service>
<activity <activity
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.MainActivity" android:name="org.kde.kdeconnect.UserInterface.MainActivity"
@@ -46,48 +39,66 @@
<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"
android:label="@string/device" android:label="@string/device"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity" android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
> >
<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.connect.UserInterface.MainActivity" />
</activity>
</activity>
<activity <activity
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.PairActivity" android:name="org.kde.kdeconnect.UserInterface.PairActivity"
android:label="@string/pair_device" android:label="@string/pair_device"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity" android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
> >
<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.connect.UserInterface.MainActivity" />
</activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"
android:label="@string/remote_control"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".DeviceActivity" />
</activity> </activity>
<activity <activity
android:name="org.kde.kdeconnect.UserInterface.SettingsActivity" android:name="org.kde.kdeconnect.UserInterface.SettingsActivity"
android:label="@string/settings" android:label="@string/settings"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceActivity" >
>
<meta-data android:name="android.support.PARENT_ACTIVITY" <meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" /> android:value=".DeviceActivity" />
</activity> </activity>
<service
android:enabled="true"
android:name="org.kde.kdeconnect.BackgroundService">
</service>
<service android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<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" />
@@ -113,43 +124,6 @@
</intent-filter>--> </intent-filter>-->
</receiver> </receiver>
<!-- Plugin-related activities and services -->
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"
android:label="@string/remote_control"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceActivity"
>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.ShareToReceiver"
android:label="KDE Connect"
>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<service android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application> </application>
</manifest> </manifest>

View File

@@ -11,10 +11,10 @@ import android.preference.PreferenceManager;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; import org.kde.kdeconnect.LinkProviders.LanLinkProvider;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity; import org.kde.kdeconnect.LinkProviders.LoopbackLinkProvider;
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 final ArrayList<BaseLinkProvider> linkProviders = new ArrayList<BaseLinkProvider>(); private ArrayList<BaseLinkProvider> linkProviders = new ArrayList<BaseLinkProvider>();
private final HashMap<String, Device> devices = new HashMap<String, Device>(); private HashMap<String, Device> devices = new HashMap<String, Device>();
private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { private 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,20 +50,18 @@ 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(getBaseContext(), deviceId);
devices.put(deviceId,device); devices.put(deviceId,device);
device.addPairingCallback(devicePairingCallback); device.addPairingCallback(devicePairingCallback);
} }
} }
} }
private void registerLinkProviders() { public void registerLinkProviders() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
@@ -81,9 +79,9 @@ public class BackgroundService extends Service {
return devices.get(id); return devices.get(id);
} }
private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
@Override @Override
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseLink link) { public void onConnectionReceived(final NetworkPackage identityPackage, final BaseComputerLink link) {
Log.i("BackgroundService", "Connection accepted!"); Log.i("BackgroundService", "Connection accepted!");
@@ -93,11 +91,11 @@ public class BackgroundService extends Service {
if (device != null) { if (device != null) {
Log.i("BackgroundService", "addLink, known device: " + deviceId); Log.i("BackgroundService", "addLink, known device: " + deviceId);
device.addLink(identityPackage, link); device.addLink(link);
} else { } else {
Log.i("BackgroundService", "addLink,unknown device: " + deviceId); Log.i("BackgroundService", "addLink,unknown device: " + deviceId);
String name = identityPackage.getString("deviceName"); String name = identityPackage.getString("deviceName");
device = new Device(BackgroundService.this, identityPackage, link); device = new Device(getBaseContext(), deviceId, name, link);
devices.put(deviceId, device); devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback); device.addPairingCallback(devicePairingCallback);
} }
@@ -106,13 +104,12 @@ public class BackgroundService extends Service {
} }
@Override @Override
public void onConnectionLost(BaseLink link) { public void onConnectionLost(BaseComputerLink link) {
Device d = devices.get(link.getDeviceId()); Device d = devices.get(link.getDeviceId());
Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
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);
} }
@@ -182,7 +179,6 @@ 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();
@@ -266,9 +262,9 @@ public class BackgroundService extends Service {
void onServiceStart(BackgroundService service); void onServiceStart(BackgroundService service);
} }
private final static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>(); private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
private final static Lock mutex = new ReentrantLock(true); private static final 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) {

View File

@@ -1,20 +1,18 @@
package org.kde.kdeconnect.Backends; package org.kde.kdeconnect.ComputerLinks;
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList; import java.util.ArrayList;
public abstract class BaseLink { public abstract class BaseComputerLink {
private final BaseLinkProvider linkProvider; private BaseLinkProvider linkProvider;
private final String deviceId; private String deviceId;
private final ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>(); private ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
protected PrivateKey privateKey;
protected BaseLink(String deviceId, BaseLinkProvider linkProvider) { protected BaseComputerLink(String deviceId, BaseLinkProvider linkProvider) {
this.linkProvider = linkProvider; this.linkProvider = linkProvider;
this.deviceId = deviceId; this.deviceId = deviceId;
} }
@@ -23,10 +21,6 @@ public abstract class BaseLink {
return deviceId; return deviceId;
} }
public void setPrivateKey(PrivateKey key) {
privateKey = key;
}
public BaseLinkProvider getLinkProvider() { public BaseLinkProvider getLinkProvider() {
return linkProvider; return linkProvider;
} }
@@ -50,8 +44,7 @@ public abstract class BaseLink {
} }
} }
//TO OVERRIDE, should be sync //TO OVERRIDE
public abstract boolean sendPackage(NetworkPackage np); public abstract boolean sendPackage(NetworkPackage np);
public abstract boolean sendPackageEncrypted(NetworkPackage np, PublicKey key);
} }

View File

@@ -0,0 +1,37 @@
package org.kde.kdeconnect.ComputerLinks;
import android.util.Log;
import org.apache.mina.core.session.IoSession;
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
public class LanComputerLink extends BaseComputerLink {
private IoSession session = null;
public void disconnect() {
Log.i("LanComputerLink","Disconnect: "+session.getRemoteAddress().toString());
session.close(true);
}
public LanComputerLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) {
super(deviceId, linkProvider);
this.session = session;
}
@Override
public boolean sendPackage(NetworkPackage np) {
if (session == null) {
Log.e("LanComputerLink","sendPackage failed: not yet connected");
return false;
} else {
session.write(np.serialize());
return true;
}
}
public void injectNetworkPackage(NetworkPackage np) {
packageReceived(np);
}
}

View File

@@ -0,0 +1,23 @@
package org.kde.kdeconnect.ComputerLinks;
import android.util.Log;
import org.apache.mina.core.session.IoSession;
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
public class LoopbackComputerLink extends BaseComputerLink {
public LoopbackComputerLink(BaseLinkProvider linkProvider) {
super("loopback", linkProvider);
}
@Override
public boolean sendPackage(NetworkPackage in) {
String s = in.serialize();
NetworkPackage out= NetworkPackage.unserialize(s);
packageReceived(out);
return true;
}
}

View File

@@ -7,6 +7,7 @@ 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;
@@ -14,7 +15,7 @@ import android.support.v4.app.NotificationCompat;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairActivity; import org.kde.kdeconnect.UserInterface.PairActivity;
@@ -33,15 +34,14 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
public class Device implements BaseLink.PackageReceiver { public class Device implements BaseComputerLink.PackageReceiver {
private final Context context; private Context context;
private final String deviceId; private String deviceId;
private final String name; private String name;
public PublicKey publicKey; private PublicKey publicKey;
private int notificationId; private int notificationId;
private int protocolVersion;
private enum PairStatus { private enum PairStatus {
NotPaired, NotPaired,
@@ -61,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 final ArrayList<BaseLink> links = new ArrayList<BaseLink>(); private ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>();
private final HashMap<String, Plugin> plugins = new HashMap<String, Plugin>(); private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private final HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>(); private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
private final SharedPreferences settings; 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) {
@@ -77,7 +77,6 @@ public class Device implements BaseLink.PackageReceiver {
this.deviceId = deviceId; this.deviceId = deviceId;
this.name = settings.getString("deviceName", "unknown device"); this.name = settings.getString("deviceName", "unknown device");
this.pairStatus = PairStatus.Paired; this.pairStatus = PairStatus.Paired;
this.protocolVersion = NetworkPackage.ProtocolVersion; //We don't know it yet
try { try {
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0); byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
@@ -91,20 +90,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, String deviceId, String name, BaseComputerLink 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 = deviceId;
this.name = np.getString("deviceName", "unidentified device"); this.name = name;
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(dl);
addLink(np, dl);
} }
public String getName() { public String getName() {
@@ -115,10 +112,6 @@ public class Device implements BaseLink.PackageReceiver {
return deviceId; return deviceId;
} }
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() {
return protocolVersion - NetworkPackage.ProtocolVersion;
}
@@ -151,55 +144,37 @@ 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) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_paired));
cb.pairingFailed(res.getString(R.string.error_already_paired));
}
return; return;
} }
if (pairStatus == PairStatus.Requested) { if (pairStatus == PairStatus.Requested) {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_requested));
cb.pairingFailed(res.getString(R.string.error_already_requested));
}
return; return;
} }
if (!isReachable()) { if (!isReachable()) {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_not_reachable));
cb.pairingFailed(res.getString(R.string.error_not_reachable));
}
return; return;
} }
//Send our own public key //Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageFinishedCallback(){ boolean success = sendPackage(np);
@Override if (!success) {
public void sendSuccessful() { for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_could_not_send_package));
if (pairingTimer != null) pairingTimer.cancel(); return;
pairingTimer = new Timer(); }
pairingTimer.schedule(new TimerTask() {
@Override
public void run() {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_timed_out));
}
Log.e("Device","Unpairing (timeout A)");
pairStatus = PairStatus.NotPaired;
}
}, 30*1000); //Time to wait for the other to accept
pairStatus = PairStatus.Requested;
}
pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() {
@Override @Override
public void sendFailed() { public void run() {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_timed_out));
cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
}
Log.e("Device","Unpairing (sendFailed A)");
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
} }
}, 20*1000);
}); pairStatus = PairStatus.Requested;
} }
@@ -211,7 +186,6 @@ public class Device implements BaseLink.PackageReceiver {
if (!isPaired()) return; 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);
@@ -227,70 +201,46 @@ public class Device implements BaseLink.PackageReceiver {
} }
private void pairingDone() { public void acceptPairing() {
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId); Log.i("Device","Accepted pairing");
if (pairingTimer != null) pairingTimer.cancel(); //Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
boolean success = sendPackage(np);
if (!success) return;
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) { for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
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 pair request started by the other device"); Log.i("Device","Rejected pairing");
//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) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
}
} }
@@ -305,27 +255,15 @@ public class Device implements BaseLink.PackageReceiver {
return !links.isEmpty(); return !links.isEmpty();
} }
public void addLink(NetworkPackage identityPackage, BaseLink link) { public void addLink(BaseComputerLink link) {
this.protocolVersion = identityPackage.getInt("protocolVersion");
links.add(link); links.add(link);
try {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
link.setPrivateKey(privateKey);
} catch (Exception e) {
e.printStackTrace();
Log.e("Device", "Exception reading our own private key"); //Should not happen
}
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<BaseComputerLink>() {
@Override @Override
public int compare(BaseLink o, BaseLink o2) { public int compare(BaseComputerLink o, BaseComputerLink o2) {
return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority(); return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority();
} }
}); });
@@ -337,7 +275,7 @@ public class Device implements BaseLink.PackageReceiver {
} }
} }
public void removeLink(BaseLink link) { public void removeLink(BaseComputerLink link) {
link.removePackageReceiver(this); link.removePackageReceiver(this);
links.remove(link); links.remove(link);
Log.i("Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size()); Log.i("Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size());
@@ -357,12 +295,9 @@ 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;
if (pairingTimer != null) pairingTimer.cancel(); pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
} }
return; return;
} }
@@ -377,9 +312,7 @@ 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) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_invalid_key));
cb.pairingFailed(context.getString(R.string.error_invalid_key));
}
return; return;
} }
@@ -387,9 +320,23 @@ public class Device implements BaseLink.PackageReceiver {
Log.i("Pairing","Pair answer"); Log.i("Pairing","Pair answer");
if (pairingTimer != null) pairingTimer.cancel(); pairStatus = PairStatus.Paired;
pairingTimer.cancel();
pairingDone(); //Store as trusted device
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).commit();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", getName());
editor.putString("publicKey", encodedPublicKey);
editor.commit();
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
} else { } else {
@@ -416,17 +363,15 @@ 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);
} }
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) }, 19*1000); //Time to show notification
pairStatus = PairStatus.RequestedByPeer; pairStatus = PairStatus.RequestedByPeer;
for (PairingCallback cb : pairingCallback) cb.incomingRequest(); for (PairingCallback cb : pairingCallback) cb.incomingRequest();
@@ -436,16 +381,13 @@ public class Device implements BaseLink.PackageReceiver {
if (pairStatus == PairStatus.Requested) { if (pairStatus == PairStatus.Requested) {
pairingTimer.cancel(); pairingTimer.cancel();
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
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();
@@ -456,6 +398,23 @@ public class Device implements BaseLink.PackageReceiver {
Log.e("onPackageReceived","Device not paired, ignoring package!"); Log.e("onPackageReceived","Device not paired, ignoring package!");
} else { } else {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
try {
//TODO: Do not read the key every time
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
np = np.decrypt(privateKey);
} catch(Exception e) {
e.printStackTrace();
Log.e("onPackageReceived","Exception reading the key needed to decrypt the package");
}
} else {
//TODO: The other side doesn't know that we are already paired, do something
Log.e("onPackageReceived","WARNING: Received unencrypted package from paired device!");
}
for (Plugin plugin : plugins.values()) { for (Plugin plugin : plugins.values()) {
plugin.onPackageReceived(np); plugin.onPackageReceived(np);
@@ -464,63 +423,36 @@ public class Device implements BaseLink.PackageReceiver {
} }
public interface SendPackageFinishedCallback {
void sendSuccessful();
void sendFailed();
}
public void sendPackage(NetworkPackage np) { public boolean sendPackage(final NetworkPackage np) {
sendPackage(np,null);
}
//Async
public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) {
final Exception backtrace = new Exception();
new Thread(new Runnable() {
@Override
public void run() {
//Log.e("sendPackage", "Sending package...");
//Log.e("sendPackage", np.serialize());
boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
//We need a copy to avoid concurrent modification exception if the original list changes
ArrayList<BaseLink> mLinks = new ArrayList<BaseLink>(links);
boolean success = false;
try {
for (BaseLink link : mLinks) {
if (useEncryption) {
success = link.sendPackageEncrypted(np, publicKey);
} else {
success = link.sendPackage(np);
}
if (success) break;
}
} catch(Exception e) {
e.printStackTrace();
Log.e("sendPackage","Error while sending package");
success = false;
}
if (success) {
// Log.e("sendPackage","Package sent");
} else {
backtrace.printStackTrace();
Log.e("sendPackage","Error: Package could not be sent ("+mLinks.size()+" links available)");
}
if (callback != null) {
if (success) callback.sendSuccessful();
else callback.sendFailed();
}
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()) {
try {
np.encrypt(publicKey);
} catch(Exception e) {
e.printStackTrace();
Log.e("Device","sendPackage exception - could not encrypt");
} }
}).start(); }
new AsyncTask<Void,Void,Void>() {
@Override
protected Void doInBackground(Void... voids) {
for(BaseComputerLink link : links) {
//Log.e("sendPackage","Trying "+link.getLinkProvider().getName());
if (link.sendPackage(np)) {
//Log.e("sent using", link.getLinkProvider().getName());
return null;
}
}
Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)");
return null;
}
}.execute();
//TODO: Detect when unable to send a package and try again somehow
return !links.isEmpty();
} }
@@ -535,7 +467,7 @@ public class Device implements BaseLink.PackageReceiver {
return plugins.get(name); return plugins.get(name);
} }
private synchronized void addPlugin(final String name) { private 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);
@@ -581,7 +513,7 @@ public class Device implements BaseLink.PackageReceiver {
} }
private synchronized boolean removePlugin(String name) { private boolean removePlugin(String name) {
Plugin plugin = plugins.remove(name); Plugin plugin = plugins.remove(name);
Plugin failedPlugin = failedPlugins.remove(name); Plugin failedPlugin = failedPlugins.remove(name);
@@ -659,7 +591,7 @@ public class Device implements BaseLink.PackageReceiver {
void onPluginsChanged(Device device); void onPluginsChanged(Device device);
} }
private final ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>(); private 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,7 +3,9 @@ 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 {
@@ -29,17 +31,14 @@ 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(PhoneLookup.DISPLAY_NAME); int nameIndex = cursor.getColumnIndex(ContactsContract.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);
cursor.close(); return name;
return name + " (" + number + ")";
} }
} }
if (cursor != null) cursor.close();
return number; return number;
} }

View File

@@ -1,17 +1,15 @@
package org.kde.kdeconnect.Helpers; package org.kde.kdeconnect;
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 DeviceHelper { public class HumanDeviceNames {
//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 final static HashMap<String,String> humanReadableNames = new HashMap<String,String>(); private 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");
@@ -156,7 +154,7 @@ public class DeviceHelper {
humanReadableNames.put("XT907","Motorola Droid Razr M"); humanReadableNames.put("XT907","Motorola Droid Razr M");
} }
public static String getDeviceName() { 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;
@@ -169,11 +167,4 @@ public class DeviceHelper {
} }
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

@@ -1,19 +1,19 @@
package org.kde.kdeconnect.Backends; package org.kde.kdeconnect.LinkProviders;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import java.util.ArrayList; import java.util.ArrayList;
public abstract class BaseLinkProvider { public abstract class BaseLinkProvider {
private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>(); private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public interface ConnectionReceiver { public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link); public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link);
public void onConnectionLost(BaseLink link); public void onConnectionLost(BaseComputerLink link);
} }
public void addConnectionReceiver(ConnectionReceiver cr) { public void addConnectionReceiver(ConnectionReceiver cr) {
@@ -25,13 +25,13 @@ public abstract class BaseLinkProvider {
} }
//These two should be called when the provider links to a new computer //These two should be called when the provider links to a new computer
protected void connectionAccepted(NetworkPackage identityPackage, BaseLink link) { protected void connectionAccepted(NetworkPackage identityPackage, BaseComputerLink link) {
Log.i("LinkProvider", "connectionAccepted"); Log.i("LinkProvider", "connectionAccepted");
for(ConnectionReceiver cr : connectionReceivers) { for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionReceived(identityPackage, link); cr.onConnectionReceived(identityPackage, link);
} }
} }
protected void connectionLost(BaseLink link) { protected void connectionLost(BaseComputerLink link) {
Log.i("LinkProvider", "connectionLost"); Log.i("LinkProvider", "connectionLost");
for(ConnectionReceiver cr : connectionReceivers) { for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionLost(link); cr.onConnectionLost(link);

View File

@@ -1,9 +1,8 @@
package org.kde.kdeconnect.Backends.LanBackend; package org.kde.kdeconnect.LinkProviders;
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;
@@ -17,7 +16,7 @@ import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor; import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.ComputerLinks.LanComputerLink;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -31,9 +30,9 @@ public class LanLinkProvider extends BaseLinkProvider {
private final static int port = 1714; private final static int port = 1714;
private final Context context; private Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>(); private HashMap<String, LanComputerLink> visibleComputers = new HashMap<String, LanComputerLink>();
private final LongSparseArray<LanLink> nioSessions = new LongSparseArray<LanLink>(); private HashMap<Long, LanComputerLink> nioSessions = new HashMap<Long, LanComputerLink>();
private NioSocketAcceptor tcpAcceptor = null; private NioSocketAcceptor tcpAcceptor = null;
private NioDatagramAcceptor udpAcceptor = null; private NioDatagramAcceptor udpAcceptor = null;
@@ -42,9 +41,8 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
public void sessionClosed(IoSession session) throws Exception { public void sessionClosed(IoSession session) throws Exception {
LanLink brokenLink = nioSessions.get(session.getId()); LanComputerLink brokenLink = nioSessions.remove(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();
@@ -60,30 +58,21 @@ 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;
if (theMessage.isEmpty()) {
Log.e("LanLinkProvider","Empty package received");
return;
}
NetworkPackage np = NetworkPackage.unserialize(theMessage); NetworkPackage np = NetworkPackage.unserialize(theMessage);
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { LanComputerLink prevLink = nioSessions.get(session.getId());
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
if (np.getString("deviceId").equals(myId)) { if (np.getString("deviceId").equals(myId)) {
return; return;
} }
LanComputerLink link = new LanComputerLink(session, np.getString("deviceId"), LanLinkProvider.this);
//Log.e("LanLinkProvider", "Identity package received from "+np.getString("deviceName"));
LanLink link = new LanLink(session, np.getString("deviceId"), LanLinkProvider.this);
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 {
@@ -94,79 +83,84 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
}; };
private final IoHandler udpHandler = new IoHandlerAdapter() { private 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);
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString()); //Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
NetworkPackage np = null;
try { try {
//We should receive a string thanks to the TextLineCodecFactory filter //We should receive a string thanks to the TextLineCodecFactory filter
String theMessage = (String) message; String theMessage = (String) message;
final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage); np = NetworkPackage.unserialize(theMessage);
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLinkProvider", "Could not unserialize package");
}
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { if (np != null) {
final NetworkPackage identityPackage = np;
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
Log.e("LanLinkProvider", "1 Expecting an identity package"); Log.e("LanLinkProvider", "1 Expecting an identity package");
return; return;
} else { } else {
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
if (identityPackage.getString("deviceId").equals(myId)) { if (np.getString("deviceId").equals(myId)) {
return; return;
} }
} }
Log.i("LanLinkProvider", "Identity package received, creating link"); Log.i("LanLinkProvider", "Identity package received, creating link");
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress(); try {
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
final NioSocketConnector connector = new NioSocketConnector(); final NioSocketConnector connector = new NioSocketConnector();
connector.setHandler(tcpHandler); connector.setHandler(tcpHandler);
//TextLineCodecFactory will split incoming data delimited by the given string //TextLineCodecFactory will split incoming data delimited by the given string
connector.getFilterChain().addLast("codec", connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter( new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX) new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
) )
); );
connector.getSessionConfig().setKeepAlive(true); connector.getSessionConfig().setKeepAlive(true);
int tcpPort = identityPackage.getInt("tcpPort",port); int tcpPort = np.getInt("tcpPort",port);
ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort)); ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
future.addListener(new IoFutureListener<IoFuture>() { future.addListener(new IoFutureListener<IoFuture>() {
@Override @Override
public void operationComplete(IoFuture ioFuture) { public void operationComplete(IoFuture ioFuture) {
final IoSession session = ioFuture.getSession(); 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()); LanComputerLink link = new LanComputerLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this);
new Thread(new Runnable() { NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
@Override link.sendPackage(np2);
public void run() {
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
link.sendPackage(np2);
nioSessions.put(session.getId(), link); nioSessions.put(session.getId(), link);
addLink(identityPackage, link); addLink(identityPackage, link);
} }
}).start(); });
} } catch (Exception e) {
}); Log.e("LanLinkProvider","Exception!!");
e.printStackTrace();
}
} catch (Exception e) {
Log.e("LanLinkProvider","Exception receiving udp package!!");
e.printStackTrace();
} }
} }
}; };
private void addLink(NetworkPackage identityPackage, LanLink link) { private void addLink(NetworkPackage identityPackage, LanComputerLink link) {
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); LanComputerLink oldLink = visibleComputers.get(deviceId);
visibleComputers.put(deviceId, link); visibleComputers.put(deviceId, link);
connectionAccepted(identityPackage, link); connectionAccepted(identityPackage, link);
if (oldLink != null) { if (oldLink != null) {

View File

@@ -0,0 +1,65 @@
package org.kde.kdeconnect.LinkProviders;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.kde.kdeconnect.ComputerLinks.LanComputerLink;
import org.kde.kdeconnect.ComputerLinks.LoopbackComputerLink;
import org.kde.kdeconnect.NetworkPackage;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.HashMap;
public class LoopbackLinkProvider extends BaseLinkProvider {
private Context context;
public LoopbackLinkProvider(Context context) {
this.context = context;
}
@Override
public void onStart() {
onNetworkChange();
}
@Override
public void onStop() {
}
@Override
public void onNetworkChange() {
NetworkPackage np = NetworkPackage.createIdentityPackage(context);
connectionAccepted(np, new LoopbackComputerLink(this));
}
@Override
public int getPriority() {
return 0;
}
@Override
public String getName() {
return "LoopbackLinkProvider";
}
}

View File

@@ -8,12 +8,9 @@ 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.InputStream;
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;
@@ -23,39 +20,29 @@ import javax.crypto.Cipher;
public class NetworkPackage { public class NetworkPackage {
public final static int ProtocolVersion = 5; public final static int ProtocolVersion = 3;
//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_SHARE = "kdeconnect.share";
private long mId; private long mId;
private String mType; private String mType;
private JSONObject mBody; private JSONObject mBody;
private InputStream mPayload;
private JSONObject mPayloadTransferInfo;
private int mPayloadSize;
private NetworkPackage() { private NetworkPackage() {
} }
public NetworkPackage(String type) { public NetworkPackage(String type) {
mId = System.currentTimeMillis(); mId = System.currentTimeMillis();
mType = type; mType = type;
mBody = new JSONObject(); mBody = new JSONObject();
mPayload = null;
mPayloadSize = 0;
mPayloadTransferInfo = new JSONObject();
} }
public String getType() { public String getType() {
@@ -75,9 +62,6 @@ 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>();
@@ -107,64 +91,40 @@ public class NetworkPackage {
} }
} }
public boolean has(String key) { return mBody.has(key); }
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); } public boolean has(String key) { return mBody.has(key); }
public String serialize() { public String serialize() {
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
try { try {
jo.put("id", mId); jo.put("id",mId);
jo.put("type", mType); jo.put("type",mType);
jo.put("body", mBody); jo.put("body",mBody);
if (hasPayload()) {
jo.put("payloadSize", mPayloadSize);
jo.put("payloadTransferInfo", mPayloadTransferInfo);
}
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace();
Log.e("NetworkPackage", "Serialization exception");
} }
//QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format. //QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
String json = jo.toString().replace("\\/","/")+"\n"; String json = jo.toString().replace("\\/","/")+"\n";
//Log.e("NetworkPackage.serialize",json);
if (!isEncrypted()) {
//Log.e("NetworkPackage.serialize", json);
}
return json; return json;
} }
static public NetworkPackage unserialize(String s) { static public NetworkPackage unserialize(String s) {
//Log.e("NetworkPackage.unserialize", s);
NetworkPackage np = new NetworkPackage(); NetworkPackage np = new NetworkPackage();
try { try {
JSONObject jo = new JSONObject(s); JSONObject jo = new JSONObject(s);
np.mId = jo.getLong("id"); np.mId = jo.getLong("id");
np.mType = jo.getString("type"); np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body"); np.mBody = jo.getJSONObject("body");
if (jo.has("payloadSize")) {
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
np.mPayloadSize = jo.getInt("payloadSize");
} else {
np.mPayloadTransferInfo = new JSONObject();
np.mPayloadSize = 0;
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
Log.e("NetworkPackage", "Unserialization exception unserializing "+s);
return null; return null;
} }
if (!np.isEncrypted()) {
//Log.e("NetworkPackage.unserialize", s);
}
return np; return np;
} }
public NetworkPackage encrypt(PublicKey publicKey) throws Exception {
public void encrypt(PublicKey publicKey) throws Exception {
String serialized = serialize(); String serialized = serialize();
@@ -186,11 +146,17 @@ public class NetworkPackage {
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP)); chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
} }
//Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks"); mId = System.currentTimeMillis();
mType = NetworkPackage.PACKAGE_TYPE_ENCRYPTED;
mBody = new JSONObject();
try {
mBody.put("data", chunks);
}catch(Exception e){
e.printStackTrace();
Log.e("NetworkPackage","Exception");
}
NetworkPackage encrypted = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_ENCRYPTED); Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
encrypted.set("data", chunks);
return encrypted;
} }
@@ -211,6 +177,7 @@ public class NetworkPackage {
return unserialize(decryptedJson); return unserialize(decryptedJson);
} }
static public NetworkPackage createIdentityPackage(Context context) { static public NetworkPackage createIdentityPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
@@ -218,22 +185,15 @@ 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", np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
PreferenceManager.getDefaultSharedPreferences(context).getString(
MainSettingsActivity.KEY_DEVICE_NAME_PREFERENCE,
DeviceHelper.getDeviceName()));
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion); np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
np.mBody.put("deviceType", DeviceHelper.isTablet()? "tablet" : "phone"); } catch (JSONException e) {
} 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);
@@ -248,40 +208,4 @@ public class NetworkPackage {
} }
public void setPayload(byte[] data) {
setPayload(new ByteArrayInputStream(data), data.length);
}
public void setPayload(InputStream stream, int size) {
mPayload = stream;
mPayloadSize = size;
}
/*public void setPayload(InputStream stream) {
setPayload(stream, -1);
}*/
public InputStream getPayload() {
return mPayload;
}
public int getPayloadSize() {
return mPayloadSize;
}
public boolean hasPayload() {
return (mPayload != null);
}
public boolean hasPayloadTransferInfo() {
return (mPayloadTransferInfo.length() > 0);
}
public JSONObject getPayloadTransferInfo() {
return mPayloadTransferInfo;
}
public void setPayloadTransferInfo(JSONObject payloadTransferInfo) {
mPayloadTransferInfo = payloadTransferInfo;
}
} }

View File

@@ -8,6 +8,7 @@ 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;
@@ -16,11 +17,9 @@ import org.kde.kdeconnect_tp.R;
public class BatteryPlugin extends Plugin { public class BatteryPlugin extends Plugin {
// keep these fields in sync with kdeconnect-kded:BatteryPlugin.h:ThresholdBatteryEvent private NetworkPackage lastPackage = null;
private static final int THRESHOLD_EVENT_NONE= 0;
private static final int THRESHOLD_EVENT_BATTERY_LOW = 1;
private NetworkPackage lastInfo = null; private IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
/*static { /*static {
PluginFactory.registerPlugin(BatteryPlugin.class); PluginFactory.registerPlugin(BatteryPlugin.class);
@@ -51,36 +50,36 @@ public class BatteryPlugin extends Plugin {
return true; return true;
} }
private final BroadcastReceiver receiver = new BroadcastReceiver() {
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent batteryIntent) { public void onReceive(Context context, Intent intent) {
Intent batteryChargeIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); Log.i("BatteryPlugin", "Battery event");
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;
if (lastInfo != null boolean isCharging = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
&& isCharging != lastInfo.getBoolean("isCharging")
&& currentCharge != lastInfo.getInt("currentCharge") int currentCharge = 100;
&& thresholdEvent != lastInfo.getInt("thresholdEvent") int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status != BatteryManager.BATTERY_STATUS_FULL) {
Intent batteryStatus = context.registerReceiver(null, filter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
currentCharge = level*100 / scale;
}
//Only notify if change is meaningful enough
if (lastPackage == null
|| (
isCharging != lastPackage.getBoolean("isCharging")
|| currentCharge != lastPackage.getInt("currentCharge")
)
) { ) {
//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("currentCharge", currentCharge);
np.set("isCharging", isCharging); np.set("isCharging", isCharging);
np.set("thresholdEvent", thresholdEvent); np.set("currentCharge", currentCharge);
device.sendPackage(np); device.sendPackage(np);
lastInfo = np; lastPackage = np;
} }
} }
@@ -88,14 +87,12 @@ public class BatteryPlugin extends Plugin {
@Override @Override
public boolean onCreate() { public boolean onCreate() {
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); context.registerReceiver(receiver, filter);
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);
} }
@@ -104,8 +101,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 (lastInfo != null) { if (lastPackage != null) {
device.sendPackage(lastInfo); device.sendPackage(lastPackage);
} }
} }

View File

@@ -10,18 +10,12 @@ 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;
private ClipboardManager.OnPrimaryClipChangedListener listener; 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
@@ -47,24 +41,12 @@ 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;
if(android.os.Build.VERSION.SDK_INT < 11) { cm.setText(text);
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
else
{
cm.setText(text);
}
} }
} }

View File

@@ -5,6 +5,7 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; 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;
@@ -38,21 +39,31 @@ public class ClipboardPlugin extends Plugin {
@Override @Override
public boolean isEnabledByDefault() { public boolean isEnabledByDefault() {
//Disabled by default due to just one direction sync(incoming clipboard change) in early version of android. return (Build.VERSION.SDK_INT >= 11);
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
@@ -65,6 +76,7 @@ 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

View File

@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
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;
@@ -14,10 +13,10 @@ import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
@@ -28,8 +27,6 @@ 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 begginning (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");
@@ -96,12 +93,8 @@ 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);
} }
} }
@@ -126,14 +119,14 @@ public class MprisActivity extends Activity {
} }
private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override @Override
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) { public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link) {
connectToPlugin(); connectToPlugin();
} }
@Override @Override
public void onConnectionLost(BaseLink link) { public void onConnectionLost(BaseComputerLink link) {
} }
}; };
@@ -149,73 +142,12 @@ 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);
deviceId = getIntent().getStringExtra("deviceId"); final String deviceId = getIntent().getStringExtra("deviceId");
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() { BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override @Override
@@ -255,36 +187,6 @@ public class MprisActivity extends Activity {
} }
}); });
findViewById(R.id.rew_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(-10000000); // -10 seconds. TODO: plugin settings UI?
}
});
}
});
findViewById(R.id.ff_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
MprisPlugin mpris = (MprisPlugin)device.getPlugin("plugin_mpris");
if (mpris == null) return;
mpris.Seek(10000000); // 10 seconds. TODO: plugin settings UI?
}
});
}
});
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() { findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
@@ -302,12 +204,10 @@ 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) {
@@ -319,14 +219,12 @@ public class MprisActivity extends Activity {
if (mpris == null) return; if (mpris == null) return;
mpris.setVolume(seekBar.getProgress()); mpris.setVolume(seekBar.getProgress());
} }
}); });
} }
}); });
} }
} }

View File

@@ -84,13 +84,6 @@ 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;

View File

@@ -5,6 +5,7 @@ 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;
@@ -15,7 +16,7 @@ public class NotificationReceiver extends NotificationListenerService {
void onNotificationRemoved(StatusBarNotification statusBarNotification); void onNotificationRemoved(StatusBarNotification statusBarNotification);
} }
private final ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>(); private ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
public void addListener(NotificationListener listener) { public void addListener(NotificationListener listener) {
listeners.add(listener); listeners.add(listener);
@@ -26,7 +27,7 @@ public class NotificationReceiver extends NotificationListenerService {
@Override @Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) { public void onNotificationPosted(StatusBarNotification statusBarNotification) {
//Log.e("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size()); Log.i("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
for(NotificationListener listener : listeners) { for(NotificationListener listener : listeners) {
listener.onNotificationPosted(statusBarNotification); listener.onNotificationPosted(statusBarNotification);
} }
@@ -48,7 +49,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.e("NotificationReceiver", "onStartCommand"); Log.i("NotificationReceiver", "onStartCommand");
for (InstanceCallback c : callbacks) { for (InstanceCallback c : callbacks) {
c.onServiceStart(this); c.onServiceStart(this);
} }
@@ -60,7 +61,7 @@ public class NotificationReceiver extends NotificationListenerService {
void onServiceStart(NotificationReceiver service); void onServiceStart(NotificationReceiver service);
} }
private final static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>(); private 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,19 +6,25 @@ 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_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 { /*static {
@@ -110,9 +116,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public boolean onCreate() { public boolean onCreate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { if (Build.VERSION.SDK_INT < 18) return false;
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");
@@ -122,10 +126,12 @@ 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) {
sendNotification(notification, true); onNotificationPosted(notification);
} }
*/
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("NotificationsPlugin","Exception"); Log.e("NotificationsPlugin","Exception");
@@ -142,11 +148,6 @@ 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) {
@@ -171,19 +172,12 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) { public void onNotificationPosted(StatusBarNotification statusBarNotification) {
sendNotification(statusBarNotification, false); onNotificationPosted(statusBarNotification, false);
} }
public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) { public void onNotificationPosted(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);
@@ -191,26 +185,25 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
String packageName = statusBarNotification.getPackageName(); String packageName = statusBarNotification.getPackageName();
String appName = AppsHelper.appNameLookup(context, packageName); String appName = AppsHelper.appNameLookup(context, packageName);
//TODO: Add support for displaying app icons to desktop plasmoid and uncomment this piece of code
/*
try { try {
//TODO: Scale down app icon if too big and compress as JPG
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName); Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon); Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ByteArrayOutputStream outStream = new ByteArrayOutputStream();
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream); appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
byte[] bitmapData = outStream.toByteArray(); byte[] bitmapData = outStream.toByteArray();
np.setPayload(bitmapData); byte[] serializedBitmapData = Base64.encode(bitmapData, Base64.NO_WRAP);
String stringBitmapData = new String(serializedBitmapData, Charset.defaultCharset());
//The icon is super big, better sending it as a file transfer when we support that
//np.set("base64icon", stringBitmapData);
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("NotificationsPlugin","Error retrieving icon"); Log.e("NotificationsPlugin","Error retrieving icon");
} }
*/
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", getTickerText(notification)); np.set("ticker", notification.tickerText.toString());
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);
@@ -218,38 +211,7 @@ 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) {
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;
}
}
if (ticker.isEmpty()) {
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
}
}
return ticker;
}
@Override @Override
@@ -262,7 +224,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
private void sendCurrentNotifications(NotificationReceiver service) { private void sendCurrentNotifications(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications(); StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) { for (StatusBarNotification notification : notifications) {
sendNotification(notification, true); onNotificationPosted(notification, true);
} }
} }

View File

@@ -4,18 +4,14 @@ 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;
@@ -67,18 +63,9 @@ 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
);
Notification noti = new NotificationCompat.Builder(context) Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName()) .setContentTitle(device.getName())
.setContentText("Ping!") .setContentText("Ping!")
.setContentIntent(resultPendingIntent)
.setTicker("Ping!") .setTicker("Ping!")
.setSmallIcon(android.R.drawable.ic_dialog_alert) .setSmallIcon(android.R.drawable.ic_dialog_alert)
.setAutoCancel(true) .setAutoCancel(true)

View File

@@ -7,7 +7,6 @@ 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.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;
@@ -50,11 +49,11 @@ public class PluginFactory {
return enabledByDefault; return enabledByDefault;
} }
private final String pluginName; private String pluginName;
private final String displayName; private String displayName;
private final String description; private String description;
private final Drawable icon; private final Drawable icon;
private final boolean enabledByDefault; private boolean enabledByDefault;
} }
@@ -67,8 +66,7 @@ public class PluginFactory {
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);
} }

View File

@@ -10,6 +10,7 @@ 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;
@@ -48,7 +49,7 @@ public class TelephonyPlugin extends Plugin {
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 intent) {
@@ -95,8 +96,6 @@ 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);
@@ -163,7 +162,6 @@ 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);

View File

@@ -24,15 +24,14 @@ import org.kde.kdeconnect_tp.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
public class DeviceActivity extends ActionBarActivity { 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 String deviceId;
private Device device; private Device device;
private final Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() { private Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
@Override @Override
public void onPluginsChanged(final Device device) { public void onPluginsChanged(final Device device) {
@@ -59,31 +58,23 @@ public class DeviceActivity extends ActionBarActivity {
errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() { errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (position == 0) return; Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to substract one
Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to subtract one
p.getErrorDialog(DeviceActivity.this).show(); p.getErrorDialog(DeviceActivity.this).show();
} }
}); });
try { //Buttons list
//Buttons list ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>(); final Collection<Plugin> plugins = device.getLoadedPlugins().values();
final Collection<Plugin> plugins = device.getLoadedPlugins().values(); for (Plugin p : plugins) {
for (Plugin p : plugins) { Button b = p.getInterfaceButton(DeviceActivity.this);
Button b = p.getInterfaceButton(DeviceActivity.this); if (b != null) {
if (b != null) { items.add(new SectionItem(p.getDisplayName()));
items.add(new SectionItem(p.getDisplayName())); items.add(new ButtonItem(b));
items.add(new ButtonItem(b));
}
} }
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
} catch(ConcurrentModificationException e) {
Log.e("DeviceActivity", "ConcurrentModificationException");
this.run(); //Try again
} }
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
} }
}); });
@@ -100,15 +91,12 @@ public class DeviceActivity extends ActionBarActivity {
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
if (getIntent().hasExtra("deviceId")) { deviceId = getIntent().getStringExtra("deviceId");
deviceId = getIntent().getStringExtra("deviceId");
}
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() { BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
@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());
device.addPluginsChangedListener(pluginsChangedListener); device.addPluginsChangedListener(pluginsChangedListener);
pluginsChangedListener.onPluginsChanged(device); pluginsChangedListener.onPluginsChanged(device);

View File

@@ -28,17 +28,6 @@ public class DeviceItem implements ListAdapter.Item {
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());
if (device.compareProtocolVersion() != 0) {
TextView summaryView = (TextView)v.findViewById(R.id.list_item_entry_summary);
summaryView.setVisibility(View.VISIBLE);
if (device.compareProtocolVersion() > 0) {
summaryView.setText(R.string.protocol_version_newer);
} else {
summaryView.setText(R.string.protocol_version_older);
}
} else {
v.findViewById(R.id.list_item_entry_summary).setVisibility(View.GONE);
}
v.setOnClickListener(new View.OnClickListener() { v.setOnClickListener(new View.OnClickListener() {
@Override @Override

View File

@@ -14,8 +14,8 @@ public class ListAdapter extends ArrayAdapter<ListAdapter.Item> {
public View inflateView(LayoutInflater layoutInflater); public View inflateView(LayoutInflater layoutInflater);
} }
private final ArrayList<Item> items; private ArrayList<Item> items;
private final LayoutInflater layoutInflater; private 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

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

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,9 +66,6 @@ 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;
} }
@@ -82,6 +79,7 @@ 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);
} }
@@ -144,13 +142,12 @@ 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.performClick(); view.callOnClick();
} }
}); });
} }
}); });
} }
}); });
} }

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 final Device.PairingCallback pairingCallback = new Device.PairingCallback() { private Device.PairingCallback pairingCallback = new Device.PairingCallback() {
@Override @Override
public void incomingRequest() { public void incomingRequest() {

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:baselineAligned="false"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">
<!--
<ImageView
android:id="@+id/list_item_entry_drawable"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="@android:drawable/ic_menu_preferences"
android:paddingLeft="9dp"/>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="6dip"
android:layout_marginTop="3dip"
android:layout_marginBottom="3dip"
android:layout_weight="0">
-->
<TextView android:id="@+id/list_item_entry_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<!--
<TextView android:id="@+id/list_item_entry_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/list_item_entry_title"
android:layout_alignLeft="@id/list_item_entry_title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" />
</RelativeLayout>
-->
</LinearLayout>

View File

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

View File

@@ -0,0 +1,21 @@
<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

@@ -5,8 +5,6 @@
<string name="pref_plugin_telephony_desc">Send notifications for SMS and calls</string> <string name="pref_plugin_telephony_desc">Send notifications for SMS and calls</string>
<string name="pref_plugin_battery">Battery report</string> <string name="pref_plugin_battery">Battery report</string>
<string name="pref_plugin_battery_desc">Periodically report battery status</string> <string name="pref_plugin_battery_desc">Periodically report battery status</string>
<string name="pref_plugin_sftp">Filesystem expose</string>
<string name="pref_plugin_sftp_desc">Allows to browse the phone\'s filesystem remotely</string>
<string name="pref_plugin_clipboard">Clipboard sync</string> <string name="pref_plugin_clipboard">Clipboard sync</string>
<string name="pref_plugin_clipboard_desc">Share the clipboard content</string> <string name="pref_plugin_clipboard_desc">Share the clipboard content</string>
<string name="pref_plugin_mpris">Multimedia remote controls</string> <string name="pref_plugin_mpris">Multimedia remote controls</string>
@@ -52,16 +50,7 @@
<string name="settings">KDE Connect Settings</string> <string name="settings">KDE Connect Settings</string>
<string name="mpris_play">Play</string> <string name="mpris_play">Play</string>
<string name="mpris_previous">Previous</string> <string name="mpris_previous">Previous</string>
<string name="mpris_rew">Rewind</string>
<string name="mpris_ff">Fast-forward</string>
<string name="mpris_next">Next</string> <string name="mpris_next">Next</string>
<string name="mpris_volume">Volume</string> <string name="mpris_volume">Volume</string>
<string name="share_to">Share To...</string>
<string name="protocol_version_older">This device uses an old protocol version</string>
<string name="protocol_version_newer">This device uses a newer protocol version</string>
<string name="general_settings">General Settings</string>
<string name="device_name">Device name</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Invalid device name</string>
</resources> </resources>

View File

@@ -1,21 +0,0 @@
#!/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,44 +1,2 @@
buildscript { // Top-level build file where you can add configuration options common to all sub-projects/modules.
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.9.+'
}
}
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
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
}
}
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 @@
#Fri Mar 07 23:56:17 CET 2014 #Wed Apr 10 15:27:10 PDT 2013
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.11-all.zip distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip

1067
icon.svg

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 35 KiB

View File

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

1
settings.gradle Normal file
View File

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

View File

@@ -1,201 +0,0 @@
package org.kde.kdeconnect.Backends.LanBackend;
import android.util.Log;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.PublicKey;
public class LanLink extends BaseLink {
private IoSession session = null;
public void disconnect() {
if (session == null) return;
//Log.i("LanLink", "Disconnect: "+session.getRemoteAddress().toString());
session.close(true);
}
public LanLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) {
super(deviceId, linkProvider);
this.session = session;
}
private Thread sendPayload(NetworkPackage np) {
try {
final InputStream stream = np.getPayload();
ServerSocket candidateServer = null;
boolean success = false;
int tcpPort = 1739;
while(!success) {
try {
candidateServer = new ServerSocket();
candidateServer.bind(new InetSocketAddress(tcpPort));
success = true;
} catch(Exception e) {
Log.e("LanLink", "Exception openning serversocket: "+e);
tcpPort++;
if (tcpPort >= 1764) {
Log.e("LanLink", "No more ports available");
return null;
}
}
}
JSONObject payloadTransferInfo = new JSONObject();
payloadTransferInfo.put("port", tcpPort);
final ServerSocket server = candidateServer;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//TODO: Timeout when waiting for a connection and close the socket
OutputStream socket = null;
try {
socket = server.accept().getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
Log.e("LanLink","Beginning to send payload");
while ((bytesRead = stream.read(buffer)) != -1) {
//Log.e("ok",""+bytesRead);
socket.write(buffer, 0, bytesRead);
}
Log.e("LanLink","Finished sending payload");
} catch(Exception e) {
e.printStackTrace();
Log.e("LanLink", "Exception with payload upload socket");
} finally {
if (socket != null) {
try { socket.close(); } catch(Exception e) { }
}
try { server.close(); } catch(Exception e) { }
}
}
});
thread.start();
np.setPayloadTransferInfo(payloadTransferInfo);
return thread;
} catch(Exception e) {
e.printStackTrace();
Log.e("LanLink", "Exception with payload upload socket");
return null;
}
}
//Blocking, do not call from main thread
@Override
public boolean sendPackage(final NetworkPackage np) {
if (session == null) {
Log.e("LanLink", "sendPackage failed: not yet connected");
return false;
}
try {
Thread thread = null;
if (np.hasPayload()) {
thread = sendPayload(np);
if (thread == null) return false;
}
WriteFuture future = session.write(np.serialize());
future.awaitUninterruptibly();
if (!future.isWritten()) return false;
if (thread != null) {
thread.join(); //Wait for thread to finish
}
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLink", "sendPackage exception");
return false;
}
}
//Blocking, do not call from main thread
@Override
public boolean sendPackageEncrypted(NetworkPackage np, PublicKey key) {
if (session == null) {
Log.e("LanLink", "sendPackage failed: not yet connected");
return false;
}
try {
Thread thread = null;
if (np.hasPayload()) {
thread = sendPayload(np);
if (thread == null) return false;
}
np = np.encrypt(key);
WriteFuture future = session.write(np.serialize());
if (!future.await().isWritten()) return false;
if (thread != null) {
thread.join(); //Wait for thread to finish
}
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLink", "sendPackageEncrypted exception");
return false;
}
}
public void injectNetworkPackage(NetworkPackage np) {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
try {
np = np.decrypt(privateKey);
} catch(Exception e) {
e.printStackTrace();
Log.e("onPackageReceived","Exception reading the key needed to decrypt the package");
}
}
if (np.hasPayloadTransferInfo()) {
try {
Socket socket = new Socket();
int tcpPort = np.getPayloadTransferInfo().getInt("port");
InetSocketAddress address = (InetSocketAddress)session.getRemoteAddress();
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
np.setPayload(socket.getInputStream(), np.getPayloadSize());
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLink", "Exception connecting to payload remote socket");
}
}
packageReceived(np);
}
}

View File

@@ -1,43 +0,0 @@
package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
import java.security.PublicKey;
public class LoopbackLink extends BaseLink {
public LoopbackLink(BaseLinkProvider linkProvider) {
super("loopback", linkProvider);
}
@Override
public boolean sendPackage(NetworkPackage in) {
String s = in.serialize();
NetworkPackage out= NetworkPackage.unserialize(s);
if (in.hasPayload()) out.setPayload(in.getPayload(), in.getPayloadSize());
packageReceived(out);
return true;
}
@Override
public boolean sendPackageEncrypted(NetworkPackage in, PublicKey key) {
try {
in = in.encrypt(key);
String s = in.serialize();
NetworkPackage out= NetworkPackage.unserialize(s);
out.decrypt(privateKey);
packageReceived(out);
if (in.hasPayload()) out.setPayload(in.getPayload(), in.getPayloadSize());
return true;
} catch(Exception e) {
e.printStackTrace();
Log.e("LoopbackLink", "Encryption exception");
return false;
}
}
}

View File

@@ -1,43 +0,0 @@
package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.content.Context;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.NetworkPackage;
public class LoopbackLinkProvider extends BaseLinkProvider {
private final Context context;
public LoopbackLinkProvider(Context context) {
this.context = context;
}
@Override
public void onStart() {
onNetworkChange();
}
@Override
public void onStop() {
}
@Override
public void onNetworkChange() {
NetworkPackage np = NetworkPackage.createIdentityPackage(context);
connectionAccepted(np, new LoopbackLink(this));
}
@Override
public int getPriority() {
return 0;
}
@Override
public String getName() {
return "LoopbackLinkProvider";
}
}

View File

@@ -1,210 +0,0 @@
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

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

View File

@@ -1,27 +0,0 @@
package org.kde.kdeconnect.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kde.kdeconnect_tp.R;
public class EntryItem implements ListAdapter.Item {
private final String title;
public EntryItem(String title) {
this.title = title;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(R.layout.list_item_entry, null);
TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
if (titleView != null) titleView.setText(title);
return v;
}
}

View File

@@ -1,112 +0,0 @@
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)
private 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

@@ -1,302 +0,0 @@
package org.kde.kdeconnect.UserInterface;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.List.EntryItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect_tp.R;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
public class ShareToReceiver extends ActionBarActivity {
//
// Action bar
//
private MenuItem menuProgress;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
menuProgress = menu.findItem(R.id.menu_progress);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
updateComputerList();
BackgroundService.RunCommand(ShareToReceiver.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.onNetworkChange();
}
});
item.setVisible(false);
menuProgress.setVisible(true);
new Thread(new Runnable() {
@Override
public void run() {
try { Thread.sleep(1500); } catch (InterruptedException e) { }
runOnUiThread(new Runnable() {
@Override
public void run() {
menuProgress.setVisible(false);
item.setVisible(true);
}
});
}
}).start();
break;
default:
break;
}
return true;
}
private void updateComputerList() {
final Intent intent = getIntent();
String action = intent.getAction();
if (!Intent.ACTION_SEND.equals(action) && !Intent.ACTION_SEND_MULTIPLE.equals(action)) {
finish();
return;
}
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
Collection<Device> devices = service.getDevices().values();
final ArrayList<Device> devicesList = new ArrayList<Device>();
final ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
items.add(new SectionItem(getString(R.string.share_to)));
for (Device d : devices) {
if (d.isReachable() && d.isPaired()) {
devicesList.add(d);
items.add(new EntryItem(d.getName()));
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
ListView list = (ListView) findViewById(R.id.listView1);
list.setAdapter(new ListAdapter(ShareToReceiver.this, items));
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Device device = devicesList.get(i-1); //NOTE: -1 because of the title!
Bundle extras = intent.getExtras();
if (extras.containsKey(Intent.EXTRA_STREAM)) {
try {
ArrayList<Uri> uriList;
if (!Intent.ACTION_SEND.equals(intent.getAction())) {
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
} else {
Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
uriList = new ArrayList<Uri>();
uriList.add(uri);
}
queuedSendUriList(device, uriList);
} catch (Exception e) {
Log.e(this.getClass().getName(), e.toString());
}
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
String text = extras.getString(Intent.EXTRA_TEXT);
boolean isUrl;
try {
new URL(text);
isUrl = true;
} catch(Exception e) {
isUrl = false;
}
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE);
if (isUrl) {
np.set("url", text);
} else {
np.set("text", text);
}
device.sendPackage(np);
}
finish();
}
});
}
});
}
});
}
private void queuedSendUriList(final Device device, final ArrayList<Uri> uriList) {
try {
Uri uri = uriList.remove(0);
ContentResolver cr = getContentResolver();
InputStream inputStream = cr.openInputStream(uri);
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE);
int size = -1;
if (uri.getScheme().equals("file")) {
// file:// is a non media uri, so we cannot query the ContentProvider
np.set("filename", uri.getLastPathSegment());
try {
size = (int)new File(uri.getPath()).length();
np.setPayload(inputStream, size);
} catch(Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file size");
}
}else{
// Probably a content:// uri, so we query the Media content provider
Cursor cursor = null;
try {
String[] proj = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME };
cursor = getContentResolver().query(uri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
cursor.moveToFirst();
String path = cursor.getString(column_index);
np.set("filename", Uri.parse(path).getLastPathSegment());
np.set("size", (int)new File(path).length());
} catch(Exception _) {
Log.e("ShareToReceiver", "Could not resolve media to a file, trying to get info as media");
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(column_index);
np.set("filename", name);
} catch (Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file name");
}
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
cursor.moveToFirst();
//For some reason this size can differ from the actual file size!
size = cursor.getInt(column_index);
} catch(Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Could not obtain file size");
}
} finally {
cursor.close();
}
np.setPayload(inputStream, size);
}
device.sendPackage(np, new Device.SendPackageFinishedCallback() {
@Override
public void sendSuccessful() {
if (!uriList.isEmpty()) queuedSendUriList(device, uriList);
else Log.e("ShareToReceiver", "All files sent");
}
@Override
public void sendFailed() {
Log.e("ShareToReceiver", "Failed to send file");
}
});
} catch (Exception e) {
e.printStackTrace();
Log.e("ShareToReceiver", "Exception sending files");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.onNetworkChange();
service.setDeviceListChangedCallback(new BackgroundService.DeviceListChangedCallback() {
@Override
public void onDeviceListChanged() {
updateComputerList();
}
});
}
});
}
@Override
protected void onStop() {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.setDeviceListChangedCallback(null);
}
});
super.onStop();
}
@Override
protected void onResume() {
super.onResume();
updateComputerList();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:baselineAligned="false"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"
android:orientation="vertical">
<TextView android:id="@+id/list_item_entry_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:text="" />
<TextView android:id="@+id/list_item_entry_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:textColor="#CC2222"
android:visibility="gone"
android:text="" />
</LinearLayout>

View File

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

View File

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

View File

@@ -1,64 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Oznamování telefonie</string>
<string name="pref_plugin_telephony_desc">Posílat upozorňování na SMS a hovory</string>
<string name="pref_plugin_battery">Hlášení baterie</string>
<string name="pref_plugin_battery_desc">Periodicky hlásit stav baterky</string>
<string name="pref_plugin_sftp_desc">Umožní vám vzdáleně prohlížet souborový systém telefonu</string>
<string name="pref_plugin_clipboard">Synchronizace schránky</string>
<string name="pref_plugin_clipboard_desc">Sdílet obsah schránky</string>
<string name="pref_plugin_mpris">Vzdálené ovládání multimédií</string>
<string name="pref_plugin_mpris_desc">Ovládejte audio/video z vašeho telefonu</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Posílat a přijímat ping</string>
<string name="pref_plugin_notifications">Synchronizace hlášení</string>
<string name="pref_plugin_notifications_desc">Zpřístupněte si upozornění z jiných zařízení</string>
<string name="plugin_not_available">Tato vlastnost není pro vaši verzi Androidu platná</string>
<string name="device_list_empty">Žádná zařízení</string>
<string name="ok">OK</string>
<string name="cancel">Zrušit</string>
<string name="open_settings">Otevřít nastavení</string>
<string name="no_permissions">Pro zpřístupnění upozornění potřebujete oprávnění</string>
<string name="send_ping">Poslat ping</string>
<string name="open_mpris_controls">Otevřít vzdálené ovládání</string>
<string name="category_connected_devices">Připojená zařízení</string>
<string name="category_not_paired_devices">Nespárovaná zařízení</string>
<string name="category_remembered_devices">Zapamatovaná zařízení</string>
<string name="plugins_failed_to_load">Moduly nebyly načteny (ťukněte pro více informací):</string>
<string name="device_menu_plugins">Vyberte moduly</string>
<string name="device_menu_unpair">Zrušit párování</string>
<string name="unknown_device">Neznámé zařízení</string>
<string name="error_not_reachable">Zařízení je nedostupné</string>
<string name="error_already_requested">Párování již bylo vyžádáno</string>
<string name="error_already_paired">Zařízení je již spárované</string>
<string name="error_could_not_send_package">Balíček nelze poslat</string>
<string name="error_timed_out">Čas vypršel</string>
<string name="error_canceled_by_user">Přerušeno uživatelem</string>
<string name="error_canceled_by_other_peer">Přerušeno druhým uživatelem</string>
<string name="error_invalid_key">Byl přijat neplatný klíč</string>
<string name="pair_requested">Bylo vyžádáno párování</string>
<string name="pairing_request_from">Požadavek o párování z %1s</string>
<string name="tap_to_answer">Ťukněte pro odpovězení</string>
<string name="reconnect">Znovu připojit</string>
<string name="device_not_paired">Zařízení není spárované</string>
<string name="request_pairing">Vyžádat párování</string>
<string name="pairing_accept">Přijmout</string>
<string name="pairing_reject">Odmítnout</string>
<string name="device">Zařízení</string>
<string name="pair_device">Spárovat zařízení</string>
<string name="remote_control">Vzdálené ovládání</string>
<string name="settings">Nastavení KDE Connect</string>
<string name="mpris_play">Přehrát</string>
<string name="mpris_previous">Předchozí</string>
<string name="mpris_rew">Přetočit zpět</string>
<string name="mpris_ff">Rychle vpřed</string>
<string name="mpris_next">Následující</string>
<string name="mpris_volume">Hlasitost</string>
<string name="share_to">Sdílet s...</string>
<string name="protocol_version_older">Toto zařízení používá starou verzi protokolu</string>
<string name="protocol_version_newer">Toto zařízení používá novější verzi protokolu</string>
<string name="general_settings">Obecná nastavení</string>
<string name="device_name">Název zařízení</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Neplatný název zařízení</string>
</resources>

View File

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

View File

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

View File

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

View File

@@ -1,63 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Puhelinilmoitukset</string>
<string name="pref_plugin_telephony_desc">Lähetä ilmoitukset tekstiviesteistä ja puheluista</string>
<string name="pref_plugin_battery">Akkuraportti</string>
<string name="pref_plugin_battery_desc">Raportoi akun tila säännöllisesti</string>
<string name="pref_plugin_clipboard">Leikepöydän synkronointi</string>
<string name="pref_plugin_clipboard_desc">Jaa leikepöydän sisältö</string>
<string name="pref_plugin_mpris">Multimediakauko-ohjaimet</string>
<string name="pref_plugin_mpris_desc">Ohjaa ääntä ja videota puhelimestasi</string>
<string name="pref_plugin_ping">Tiedustelupaketti</string>
<string name="pref_plugin_ping_desc">Lähetä ja vastaanota tiedustelupaketteja</string>
<string name="pref_plugin_notifications">Ilmoitusten synkronointi</string>
<string name="pref_plugin_notifications_desc">Näe ilmoituksesi muissa laitteissa</string>
<string name="plugin_not_available">Piirre ei ole käytettävissä Android-versiossasi</string>
<string name="device_list_empty">Ei laitteita</string>
<string name="ok">OK</string>
<string name="cancel">Peru</string>
<string name="open_settings">Avaa asetukset</string>
<string name="no_permissions">Sinun on annettava lupa ilmoitusten näkemiseen</string>
<string name="send_ping">Lähetä tiedustelupaketti</string>
<string name="open_mpris_controls">Avaa kauko-ohjain</string>
<string name="category_connected_devices">Yhdistetyt laitteet</string>
<string name="category_not_paired_devices">Ei laitepareja</string>
<string name="category_remembered_devices">Muistetut laitteet</string>
<string name="plugins_failed_to_load">Liitännäisten lataus epäonnistui (lisätietoa napsauttamalla):</string>
<string name="device_menu_plugins">Valitse liitännäiset</string>
<string name="device_menu_unpair">Irrota paritus</string>
<string name="unknown_device">Tuntematon laite</string>
<string name="error_not_reachable">Laite tavoittamattomissa</string>
<string name="error_already_requested">Paritusta on jo pyydetty</string>
<string name="error_already_paired">Laitepari on jo muodostettu</string>
<string name="error_could_not_send_package">Ei voitu lähettää pakettia</string>
<string name="error_timed_out">Aikakatkaisu</string>
<string name="error_canceled_by_user">Käyttäjä perui</string>
<string name="error_canceled_by_other_peer">Vertaiskäyttäjä perui</string>
<string name="error_invalid_key">Vastaanotettiin väärä avain</string>
<string name="pair_requested">Parituspyyntö</string>
<string name="pairing_request_from">Parituspyyntö kohteesta %1s</string>
<string name="tap_to_answer">Vastaa napsauttamalla</string>
<string name="reconnect">Yhdistä uudelleen</string>
<string name="device_not_paired">Ei laiteparia</string>
<string name="request_pairing">Pyydä paritusta</string>
<string name="pairing_accept">Hyväksy</string>
<string name="pairing_reject">Hylkää</string>
<string name="device">Laite</string>
<string name="pair_device">Parita laite</string>
<string name="remote_control">Kauko-ohjain</string>
<string name="settings">KDE Connectin asetukset</string>
<string name="mpris_play">Toista</string>
<string name="mpris_previous">Edellinen</string>
<string name="mpris_rew">Kelaa takaisin</string>
<string name="mpris_ff">Nopeasti eteenpäin</string>
<string name="mpris_next">Seuraava</string>
<string name="mpris_volume">Äänenvoimakkuus</string>
<string name="share_to">Jaa…</string>
<string name="protocol_version_older">Laite käyttää vanha yhteyskäytäntöversiota</string>
<string name="protocol_version_newer">Laite käyttää uudempaa yhteyskäytäntöversiota</string>
<string name="general_settings">Yleisasetukset</string>
<string name="device_name">Laitenimi</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Virheellinen laitenimi</string>
</resources>

View File

@@ -1,65 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="pref_plugin_telephony">Notificateur de téléphonie</string>
<string name="pref_plugin_telephony_desc">Envoie des notifications pour les SMS et les appels</string>
<string name="pref_plugin_battery">Rapport sur la batterie</string>
<string name="pref_plugin_battery_desc">Rapport périodique sur l\'état de la batterie</string>
<string name="pref_plugin_sftp">Exposer le système de fichiers</string>
<string name="pref_plugin_sftp_desc">Permettre de parcourir le système de fichiers du téléphone à distance</string>
<string name="pref_plugin_clipboard">Synchronisation avec le presse-papiers</string>
<string name="pref_plugin_clipboard_desc">Partage le contenu du presse-papiers</string>
<string name="pref_plugin_mpris">Contrôles distants multimédia</string>
<string name="pref_plugin_mpris_desc">Contrôle l\'audio / la vidéo depuis votre téléphone</string>
<string name="pref_plugin_ping">Commande « Ping »</string>
<string name="pref_plugin_ping_desc">Envoie et reçoit des commandes « Ping »</string>
<string name="pref_plugin_notifications">Synchronisation des notifications</string>
<string name="pref_plugin_notifications_desc">Accédez à vos notifications depuis d\'autres périphériques</string>
<string name="plugin_not_available">Cette fonctionnalité n\'est pas disponible dans votre version de Android</string>
<string name="device_list_empty">Pas de périphériques</string>
<string name="ok">Ok</string>
<string name="cancel">Annuler</string>
<string name="open_settings">Accéder aux paramètres</string>
<string name="no_permissions">Vous devez accorder la permission d\'accéder aux notifications</string>
<string name="send_ping">Envoyer un « Ping »</string>
<string name="open_mpris_controls">Ouvrir un contrôle distant</string>
<string name="category_connected_devices">Périphériques connectés</string>
<string name="category_not_paired_devices">Pas de périphériques appariés</string>
<string name="category_remembered_devices">Périphériques mémorisés</string>
<string name="plugins_failed_to_load">Le chargement des modules externes a échoué (cliquez pour plus d\'informations) :</string>
<string name="device_menu_plugins">Sélectionner des modules externes</string>
<string name="device_menu_unpair">Désapparier</string>
<string name="unknown_device">Périphérique inconnu</string>
<string name="error_not_reachable">Périphérique inaccessible</string>
<string name="error_already_requested">L\'appariement a déjà été demandé</string>
<string name="error_already_paired">Périphérique déjà apparié</string>
<string name="error_could_not_send_package">Il est impossible d\'envoyer un paquet</string>
<string name="error_timed_out">Délai expiré</string>
<string name="error_canceled_by_user">Annulé par l\'utilisateur</string>
<string name="error_canceled_by_other_peer">Annulé par un autre homologue</string>
<string name="error_invalid_key">Clé reçue non valable</string>
<string name="pair_requested">Paire demandée</string>
<string name="pairing_request_from">Demande d\'appariement provenant de %1s</string>
<string name="tap_to_answer">Cliquer pour répondre</string>
<string name="reconnect">Reconnecter</string>
<string name="device_not_paired">Périphérique non apparié</string>
<string name="request_pairing">Demande d\'appariement</string>
<string name="pairing_accept">Accepter</string>
<string name="pairing_reject">Rejeter</string>
<string name="device">Périphérique</string>
<string name="pair_device">Apparier un périphérique</string>
<string name="remote_control">Contrôle distant</string>
<string name="settings">Paramètres de connexion de KDE</string>
<string name="mpris_play">Lire</string>
<string name="mpris_previous">Précédent</string>
<string name="mpris_rew">Retour rapide</string>
<string name="mpris_ff">Avance rapide</string>
<string name="mpris_next">Suivant</string>
<string name="mpris_volume">Volume</string>
<string name="share_to">Partager vers...</string>
<string name="protocol_version_older">Le périphérique utilise une version ancienne du protocole</string>
<string name="protocol_version_newer">Le périphérique utilise une version plus récente du protocole</string>
<string name="general_settings">Paramètres généraux</string>
<string name="device_name">Nom du périphérique</string>
<string name="device_name_preference_summary">%s</string>
<string name="invalid_device_name">Nom de périphérique non valable</string>
</resources>

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