Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4b82a2ef3f |
16
.gitignore
vendored
@@ -1,14 +1,4 @@
|
||||
local.properties
|
||||
.gradle/
|
||||
.idea/
|
||||
out/
|
||||
gen/
|
||||
bin/
|
||||
build/
|
||||
target/
|
||||
classes/
|
||||
gradle
|
||||
gradlew
|
||||
gradlew.bat
|
||||
*.iml
|
||||
*.keystore
|
||||
.gradle
|
||||
.idea
|
||||
out
|
||||
|
@@ -1,3 +0,0 @@
|
||||
REVIEWBOARD_URL = "https://git.reviewboard.kde.org"
|
||||
REPOSITORY = 'git://anongit.kde.org/kdeconnect-android'
|
||||
TARGET_GROUPS = 'kdeconnect'
|
@@ -1,197 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="800"
|
||||
android:versionName="0.8">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9"
|
||||
android:targetSdkVersion="21" />
|
||||
|
||||
<supports-screens
|
||||
android:smallScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true"
|
||||
android:anyDensity="true"
|
||||
/>
|
||||
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:required="false" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="KDE Connect"
|
||||
>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name="org.kde.kdeconnect.BackgroundService">
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
android:label="KDE Connect" >
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.UserInterface.MainSettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.DeviceActivity"
|
||||
android:label="@string/device"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.PairActivity"
|
||||
android:label="@string/pair_device"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.UserInterface.SettingsActivity"
|
||||
android:label="@string/device_menu_plugins"
|
||||
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:name="org.kde.kdeconnect.UserInterface.CustomDevicesActivity"
|
||||
android:label="@string/custom_devices_settings"
|
||||
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:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
|
||||
android:label="@string/device_menu_plugins"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.SettingsActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.SettingsActivity" />
|
||||
</activity>
|
||||
|
||||
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<data android:scheme="package" android:path="org.kde.kdeconnect" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
<!--<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BATTERY_CHANGED" />
|
||||
</intent-filter>-->
|
||||
</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.Plugins.MousePadPlugin.MousePadActivity"
|
||||
android:label="@string/remote_control"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareActivity"
|
||||
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>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationFilterActivity"
|
||||
android:label="@string/title_activity_notification_filter"
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.PluginSettingsActivity" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.PluginSettingsActivity" />
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
339
COPYING
@@ -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
@@ -0,0 +1 @@
|
||||
build/
|
79
KdeConnect/KdeConnect.iml
Normal 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
@@ -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
|
||||
}
|
||||
}
|
BIN
KdeConnect/libs/android-support-v7-appcompat.jar
Normal file
BIN
KdeConnect/libs/mina-core-2.0.7.jar
Normal file
BIN
KdeConnect/libs/slf4j-api-1.6.6.jar
Normal file
129
KdeConnect/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="4"
|
||||
android:versionName="0.2.1">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9"
|
||||
android:targetSdkVersion="18" />
|
||||
|
||||
<supports-screens
|
||||
android:smallScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true"
|
||||
android:anyDensity="true"
|
||||
/>
|
||||
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:required="false"/>
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false"/>
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="KDE Connect"
|
||||
>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
android:label="KDE Connect" >
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.DeviceActivity"
|
||||
android:label="@string/device"
|
||||
android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.connect.UserInterface.MainActivity" />
|
||||
|
||||
</activity>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.kdeconnect.UserInterface.PairActivity"
|
||||
android:label="@string/pair_device"
|
||||
android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
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
|
||||
android:name="org.kde.kdeconnect.UserInterface.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".DeviceActivity" />
|
||||
</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">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<data android:scheme="package" android:path="org.kde.kdeconnect" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
<!--<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BATTERY_CHANGED" />
|
||||
</intent-filter>-->
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.app.Service;
|
||||
@@ -31,10 +11,10 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
|
||||
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
|
||||
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.LinkProviders.LanLinkProvider;
|
||||
import org.kde.kdeconnect.LinkProviders.LoopbackLinkProvider;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -46,11 +26,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
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
|
||||
public void incomingRequest() {
|
||||
if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
|
||||
@@ -70,20 +50,18 @@ public class BackgroundService extends Service {
|
||||
};
|
||||
|
||||
private void loadRememberedDevicesFromSettings() {
|
||||
//Log.e("BackgroundService", "Loading remembered trusted devices");
|
||||
SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
Set<String> trustedDevices = preferences.getAll().keySet();
|
||||
for(String deviceId : trustedDevices) {
|
||||
//Log.e("BackgroundService", "Loading device "+deviceId);
|
||||
if (preferences.getBoolean(deviceId, false)) {
|
||||
Device device = new Device(this, deviceId);
|
||||
Device device = new Device(getBaseContext(), deviceId);
|
||||
devices.put(deviceId,device);
|
||||
device.addPairingCallback(devicePairingCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLinkProviders() {
|
||||
public void registerLinkProviders() {
|
||||
|
||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
@@ -101,9 +79,9 @@ public class BackgroundService extends Service {
|
||||
return devices.get(id);
|
||||
}
|
||||
|
||||
private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
|
||||
private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
|
||||
@Override
|
||||
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseLink link) {
|
||||
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseComputerLink link) {
|
||||
|
||||
Log.i("BackgroundService", "Connection accepted!");
|
||||
|
||||
@@ -113,10 +91,11 @@ public class BackgroundService extends Service {
|
||||
|
||||
if (device != null) {
|
||||
Log.i("BackgroundService", "addLink, known device: " + deviceId);
|
||||
device.addLink(identityPackage, link);
|
||||
device.addLink(link);
|
||||
} else {
|
||||
Log.i("BackgroundService", "addLink,unknown device: " + deviceId);
|
||||
device = new Device(BackgroundService.this, identityPackage, link);
|
||||
String name = identityPackage.getString("deviceName");
|
||||
device = new Device(getBaseContext(), deviceId, name, link);
|
||||
devices.put(deviceId, device);
|
||||
device.addPairingCallback(devicePairingCallback);
|
||||
}
|
||||
@@ -125,13 +104,12 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionLost(BaseLink link) {
|
||||
public void onConnectionLost(BaseComputerLink link) {
|
||||
Device d = devices.get(link.getDeviceId());
|
||||
Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
|
||||
if (d != null) {
|
||||
d.removeLink(link);
|
||||
if (!d.isReachable() && !d.isPaired()) {
|
||||
//Log.e("onConnectionLost","Removing connection device because it was not paired");
|
||||
devices.remove(link.getDeviceId());
|
||||
d.removePairingCallback(devicePairingCallback);
|
||||
}
|
||||
@@ -201,7 +179,6 @@ public class BackgroundService extends Service {
|
||||
Log.i("BackgroundService","Service not started yet, initializing...");
|
||||
|
||||
initializeRsaKeys();
|
||||
MainSettingsActivity.initializeDeviceName(this);
|
||||
loadRememberedDevicesFromSettings();
|
||||
registerLinkProviders();
|
||||
|
||||
@@ -233,7 +210,7 @@ public class BackgroundService extends Service {
|
||||
SharedPreferences.Editor edit = settings.edit();
|
||||
edit.putString("publicKey",Base64.encodeToString(publicKey, 0).trim()+"\n");
|
||||
edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
|
||||
edit.apply();
|
||||
edit.commit();
|
||||
|
||||
}
|
||||
|
||||
@@ -285,13 +262,14 @@ public class BackgroundService extends 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
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
//This will be called for each intent launch, even if the service is already started and it is reused
|
||||
Log.i("BackgroundService","onStartCommand");
|
||||
mutex.lock();
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
@@ -0,0 +1,50 @@
|
||||
package org.kde.kdeconnect.ComputerLinks;
|
||||
|
||||
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public abstract class BaseComputerLink {
|
||||
|
||||
private BaseLinkProvider linkProvider;
|
||||
private String deviceId;
|
||||
private ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
|
||||
|
||||
protected BaseComputerLink(String deviceId, BaseLinkProvider linkProvider) {
|
||||
this.linkProvider = linkProvider;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public BaseLinkProvider getLinkProvider() {
|
||||
return linkProvider;
|
||||
}
|
||||
|
||||
|
||||
public interface PackageReceiver {
|
||||
public void onPackageReceived(NetworkPackage np);
|
||||
}
|
||||
|
||||
public void addPackageReceiver(PackageReceiver pr) {
|
||||
receivers.add(pr);
|
||||
}
|
||||
public void removePackageReceiver(PackageReceiver pr) {
|
||||
receivers.remove(pr);
|
||||
}
|
||||
|
||||
//Should be called from a background thread listening to packages
|
||||
protected void packageReceived(NetworkPackage np) {
|
||||
for(PackageReceiver pr : receivers) {
|
||||
pr.onPackageReceived(np);
|
||||
}
|
||||
}
|
||||
|
||||
//TO OVERRIDE
|
||||
public abstract boolean sendPackage(NetworkPackage np);
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.app.Notification;
|
||||
@@ -27,6 +7,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -34,7 +15,7 @@ import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Base64;
|
||||
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.PluginFactory;
|
||||
import org.kde.kdeconnect.UserInterface.PairActivity;
|
||||
@@ -46,20 +27,21 @@ import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
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 final String name;
|
||||
public PublicKey publicKey;
|
||||
private String deviceId;
|
||||
private String name;
|
||||
private PublicKey publicKey;
|
||||
private int notificationId;
|
||||
private int protocolVersion;
|
||||
|
||||
private enum PairStatus {
|
||||
NotPaired,
|
||||
@@ -79,11 +61,11 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>();
|
||||
private Timer pairingTimer;
|
||||
|
||||
private final ArrayList<BaseLink> links = new ArrayList<BaseLink>();
|
||||
private final HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
|
||||
private final HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
|
||||
private ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>();
|
||||
private HashMap<String, Plugin> plugins = 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
|
||||
Device(Context context, String deviceId) {
|
||||
@@ -95,7 +77,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
this.deviceId = deviceId;
|
||||
this.name = settings.getString("deviceName", "unknown device");
|
||||
this.pairStatus = PairStatus.Paired;
|
||||
this.protocolVersion = NetworkPackage.ProtocolVersion; //We don't know it yet
|
||||
|
||||
try {
|
||||
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
|
||||
@@ -109,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(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");
|
||||
|
||||
this.context = context;
|
||||
this.deviceId = np.getString("deviceId");
|
||||
this.name = np.getString("deviceName", "unidentified device");
|
||||
this.protocolVersion = np.getInt("protocolVersion");
|
||||
this.deviceId = deviceId;
|
||||
this.name = name;
|
||||
this.pairStatus = PairStatus.NotPaired;
|
||||
this.publicKey = null;
|
||||
|
||||
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
|
||||
addLink(np, dl);
|
||||
addLink(dl);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -133,10 +112,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -169,54 +144,37 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
Resources res = context.getResources();
|
||||
|
||||
if (pairStatus == PairStatus.Paired) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(res.getString(R.string.error_already_paired));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_paired));
|
||||
return;
|
||||
}
|
||||
if (pairStatus == PairStatus.Requested) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(res.getString(R.string.error_already_requested));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_already_requested));
|
||||
return;
|
||||
}
|
||||
if (!isReachable()) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(res.getString(R.string.error_not_reachable));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_not_reachable));
|
||||
return;
|
||||
}
|
||||
|
||||
//Send our own public key
|
||||
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
|
||||
sendPackage(np, new SendPackageStatusCallback(){
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (pairingTimer != null) pairingTimer.cancel();
|
||||
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;
|
||||
}
|
||||
boolean success = sendPackage(np);
|
||||
|
||||
if (!success) {
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_could_not_send_package));
|
||||
return;
|
||||
}
|
||||
|
||||
pairingTimer = new Timer();
|
||||
pairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_could_not_send_package));
|
||||
}
|
||||
Log.e("Device","Unpairing (sendFailed A)");
|
||||
public void run() {
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_timed_out));
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
}
|
||||
}, 20*1000);
|
||||
|
||||
});
|
||||
pairStatus = PairStatus.Requested;
|
||||
|
||||
}
|
||||
|
||||
@@ -226,11 +184,12 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
public void unpair() {
|
||||
|
||||
//Log.e("Device","Unpairing (unpair)");
|
||||
if (!isPaired()) return;
|
||||
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
preferences.edit().remove(deviceId).apply();
|
||||
preferences.edit().remove(deviceId).commit();
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
@@ -242,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;
|
||||
|
||||
//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).apply();
|
||||
preferences.edit().putBoolean(deviceId,true).commit();
|
||||
|
||||
//Store device information needed to create a Device object in a future
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.putString("deviceName", getName());
|
||||
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
editor.apply();
|
||||
editor.commit();
|
||||
|
||||
reloadPluginsFromSettings();
|
||||
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingSuccessful();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void acceptPairing() {
|
||||
|
||||
Log.i("Device","Accepted pair request started by the other device");
|
||||
|
||||
//Send our own public key
|
||||
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
|
||||
sendPackage(np, new SendPackageStatusCallback() {
|
||||
@Override
|
||||
protected void onSuccess() {
|
||||
pairingDone();
|
||||
}
|
||||
@Override
|
||||
protected void onFailure(Throwable e) {
|
||||
Log.e("Device","Unpairing (sendFailed B)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_not_reachable));
|
||||
}
|
||||
}
|
||||
});
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
np.set("pair", false);
|
||||
sendPackage(np);
|
||||
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
|
||||
|
||||
}
|
||||
|
||||
@@ -320,32 +255,18 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return !links.isEmpty();
|
||||
}
|
||||
|
||||
public void addLink(NetworkPackage identityPackage, BaseLink link) {
|
||||
|
||||
this.protocolVersion = identityPackage.getInt("protocolVersion");
|
||||
public void addLink(BaseComputerLink 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());
|
||||
|
||||
/*
|
||||
Collections.sort(links, new Comparator<BaseLink>() {
|
||||
Collections.sort(links, new Comparator<BaseComputerLink>() {
|
||||
@Override
|
||||
public int compare(BaseLink o, BaseLink o2) {
|
||||
public int compare(BaseComputerLink o, BaseComputerLink o2) {
|
||||
return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority();
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
link.addPackageReceiver(this);
|
||||
|
||||
@@ -354,7 +275,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
public void removeLink(BaseLink link) {
|
||||
public void removeLink(BaseComputerLink link) {
|
||||
link.removePackageReceiver(this);
|
||||
links.remove(link);
|
||||
Log.i("Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size());
|
||||
@@ -374,12 +295,9 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
if (wantsPair == isPaired()) {
|
||||
if (pairStatus == PairStatus.Requested) {
|
||||
//Log.e("Device","Unpairing (pair rejected)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
if (pairingTimer != null) pairingTimer.cancel();
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
pairingTimer.cancel();
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -394,9 +312,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("Device","Pairing exception: Received incorrect key");
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_invalid_key));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_invalid_key));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -404,9 +320,23 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -425,7 +355,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
.setTicker(res.getString(R.string.pair_requested))
|
||||
.setSmallIcon(android.R.drawable.ic_menu_help)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setDefaults(Notification.DEFAULT_SOUND)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -433,17 +363,15 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
notificationId = (int)System.currentTimeMillis();
|
||||
notificationManager.notify(notificationId, noti);
|
||||
|
||||
if (pairingTimer != null) pairingTimer.cancel();
|
||||
pairingTimer = new Timer();
|
||||
|
||||
pairingTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.e("Device","Unpairing (timeout B)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}, 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;
|
||||
for (PairingCallback cb : pairingCallback) cb.incomingRequest();
|
||||
|
||||
@@ -453,123 +381,93 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
if (pairStatus == PairStatus.Requested) {
|
||||
pairingTimer.cancel();
|
||||
for (PairingCallback cb : pairingCallback) {
|
||||
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
}
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
|
||||
} else if (pairStatus == PairStatus.Paired) {
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
preferences.edit().remove(deviceId).apply();
|
||||
preferences.edit().remove(deviceId).commit();
|
||||
reloadPluginsFromSettings();
|
||||
}
|
||||
|
||||
//Log.e("Device","Unpairing (unpair request)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
for (PairingCallback cb : pairingCallback) cb.unpaired();
|
||||
|
||||
}
|
||||
} else if (!isPaired()) {
|
||||
|
||||
unpair();
|
||||
//TODO: Notify the other side that we don't trust them
|
||||
Log.e("onPackageReceived","Device not paired, ignoring package!");
|
||||
|
||||
} 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()) {
|
||||
try {
|
||||
plugin.onPackageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("Device", "Exception in "+plugin.getDisplayName()+"'s onPackageReceived()");
|
||||
}
|
||||
|
||||
plugin.onPackageReceived(np);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class SendPackageStatusCallback {
|
||||
protected abstract void onSuccess();
|
||||
protected abstract void onFailure(Throwable e);
|
||||
protected void onProgressChanged(int percent) { }
|
||||
|
||||
private boolean success = false;
|
||||
public void sendSuccess() {
|
||||
success = true;
|
||||
onSuccess();
|
||||
}
|
||||
public void sendFailure(Throwable e) {
|
||||
if (e != null) {
|
||||
public boolean sendPackage(final NetworkPackage np) {
|
||||
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()) {
|
||||
try {
|
||||
np.encrypt(publicKey);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("sendPackage", "Exception: " + e.getMessage());
|
||||
} else {
|
||||
Log.e("sendPackage", "Unknown (null) exception");
|
||||
Log.e("Device","sendPackage exception - could not encrypt");
|
||||
}
|
||||
onFailure(e);
|
||||
}
|
||||
public void sendProgress(int percent) { onProgressChanged(percent); }
|
||||
}
|
||||
|
||||
|
||||
public void sendPackage(NetworkPackage np) {
|
||||
sendPackage(np,new SendPackageStatusCallback() {
|
||||
new AsyncTask<Void,Void,Void>() {
|
||||
@Override
|
||||
protected void onSuccess() { }
|
||||
@Override
|
||||
protected void onFailure(Throwable e) { }
|
||||
});
|
||||
}
|
||||
|
||||
//Async
|
||||
public void sendPackage(final NetworkPackage np, final SendPackageStatusCallback callback) {
|
||||
|
||||
//Log.e("sendPackage", "Sending package...");
|
||||
//Log.e("sendPackage", np.serialize());
|
||||
|
||||
final Throwable backtrace = new Throwable();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
|
||||
|
||||
//Make a copy to avoid concurrent modification exception if the original list changes
|
||||
ArrayList<BaseLink> mLinks = new ArrayList<BaseLink>(links);
|
||||
for (final BaseLink link : mLinks) {
|
||||
if (useEncryption) {
|
||||
link.sendPackageEncrypted(np, callback, publicKey);
|
||||
} else {
|
||||
link.sendPackage(np, callback);
|
||||
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;
|
||||
}
|
||||
if (callback.success) break; //If the link didn't call sendSuccess(), try the next one
|
||||
}
|
||||
|
||||
if (!callback.success) {
|
||||
Log.e("sendPackage", "No device link (of "+mLinks.size()+" available) could send the package. Package lost!");
|
||||
backtrace.printStackTrace();
|
||||
}
|
||||
|
||||
Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)");
|
||||
return null;
|
||||
}
|
||||
}).start();
|
||||
}.execute();
|
||||
|
||||
//TODO: Detect when unable to send a package and try again somehow
|
||||
|
||||
return !links.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Plugin-related functions
|
||||
//
|
||||
|
||||
public Plugin getPlugin(String name) {
|
||||
return getPlugin(name, false);
|
||||
return plugins.get(name);
|
||||
}
|
||||
|
||||
public Plugin getPlugin(String name, boolean includeFailed) {
|
||||
Plugin plugin = plugins.get(name);
|
||||
if (includeFailed && plugin == null) {
|
||||
plugin = failedPlugins.get(name);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private synchronized void addPlugin(final String name) {
|
||||
private void addPlugin(final String name) {
|
||||
Plugin existing = plugins.get(name);
|
||||
if (existing != null) {
|
||||
Log.w("addPlugin","plugin already present:" + name);
|
||||
@@ -587,24 +485,24 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
boolean success;
|
||||
try {
|
||||
success = plugin.onCreate();
|
||||
boolean success = plugin.onCreate();
|
||||
if (!success) {
|
||||
Log.e("addPlugin", "plugin failed to load " + name);
|
||||
failedPlugins.put(name, plugin);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
failedPlugins.put(name, plugin);
|
||||
e.printStackTrace();
|
||||
Log.e("addPlugin", "Exception loading plugin " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
//Log.e("addPlugin","added " + name);
|
||||
failedPlugins.remove(name);
|
||||
plugins.put(name, plugin);
|
||||
} else {
|
||||
Log.e("addPlugin", "plugin failed to load " + name);
|
||||
plugins.remove(name);
|
||||
failedPlugins.put(name, plugin);
|
||||
}
|
||||
//Log.e("addPlugin","added " + name);
|
||||
|
||||
failedPlugins.remove(name);
|
||||
plugins.put(name, plugin);
|
||||
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(Device.this);
|
||||
@@ -615,14 +513,13 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
}
|
||||
|
||||
private synchronized boolean removePlugin(String name) {
|
||||
private boolean removePlugin(String name) {
|
||||
|
||||
Plugin plugin = plugins.remove(name);
|
||||
Plugin failedPlugin = failedPlugins.remove(name);
|
||||
|
||||
if (plugin == null) {
|
||||
if (failedPlugin == null) {
|
||||
//Not found
|
||||
return false;
|
||||
}
|
||||
plugin = failedPlugin;
|
||||
@@ -630,22 +527,25 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
|
||||
try {
|
||||
plugin.onDestroy();
|
||||
//Log.e("removePlugin","removed " + name);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("removePlugin","Exception calling onDestroy for plugin "+name);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.e("removePlugin","removed " + name);
|
||||
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public void setPluginEnabled(String pluginName, boolean value) {
|
||||
settings.edit().putBoolean(pluginName,value).apply();
|
||||
if (value && isPaired() && isReachable()) addPlugin(pluginName);
|
||||
settings.edit().putBoolean(pluginName,value).commit();
|
||||
if (value) addPlugin(pluginName);
|
||||
else removePlugin(pluginName);
|
||||
}
|
||||
|
||||
@@ -655,9 +555,6 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean hasPluginsLoaded() {
|
||||
return !plugins.isEmpty() || !failedPlugins.isEmpty();
|
||||
}
|
||||
|
||||
public void reloadPluginsFromSettings() {
|
||||
|
||||
@@ -677,7 +574,9 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
//No need to call PluginsChangedListeners because addPlugin and removePlugin already do so
|
||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||
listener.onPluginsChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String,Plugin> getLoadedPlugins() {
|
||||
@@ -692,7 +591,7 @@ public class Device implements BaseLink.PackageReceiver {
|
||||
void onPluginsChanged(Device device);
|
||||
}
|
||||
|
||||
private final ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
||||
private ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
||||
|
||||
public void addPluginsChangedListener(PluginsChangedListener listener) {
|
||||
pluginsChangedListeners.add(listener);
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -33,7 +13,7 @@ public class AppsHelper {
|
||||
try {
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
|
||||
ApplicationInfo ai = pm.getApplicationInfo( packageName, 0);
|
||||
|
||||
return pm.getApplicationLabel(ai).toString();
|
||||
|
||||
@@ -69,4 +49,4 @@ public class AppsHelper {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -1,29 +1,11 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Log;
|
||||
|
||||
public class ContactsHelper {
|
||||
|
||||
@@ -49,18 +31,15 @@ public class ContactsHelper {
|
||||
|
||||
// Take the first match only
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int nameIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
|
||||
int nameIndex = cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
String name = cursor.getString(nameIndex);
|
||||
//Log.e("PhoneNumberLookup", "success: " + name);
|
||||
cursor.close();
|
||||
return name + " (" + number + ")";
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor != null) cursor.close();
|
||||
|
||||
return number;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class ImagesHelper {
|
||||
|
||||
public static Bitmap drawableToBitmap (Drawable drawable) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable)drawable).getBitmap();
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,170 @@
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class HumanDeviceNames {
|
||||
|
||||
//from https://github.com/meetup/android-device-names
|
||||
//Converted to java using:
|
||||
//cat android_models.properties | awk -F'=' '{sub(/ *$/, "", $1)} sub(/^ */, "", $2) { if ($2 != "") print "humanReadableNames.put(\""$1"\",\"" $2 "\");"}'
|
||||
private static HashMap<String,String> humanReadableNames = new HashMap<String,String>();
|
||||
static {
|
||||
humanReadableNames.put("5860E","Coolpad Quattro 4G");
|
||||
humanReadableNames.put("ADR6300","HTC Droid Incredible");
|
||||
humanReadableNames.put("ADR6330VW","HTC Rhyme");
|
||||
humanReadableNames.put("ADR6350","HTC Droid Incredible 2");
|
||||
humanReadableNames.put("ADR6400L","HTC Thunderbolt");
|
||||
humanReadableNames.put("ADR6410LVW","HTC Droid Incredible 4G");
|
||||
humanReadableNames.put("ADR6425LVW","HTC Rezound 4G");
|
||||
humanReadableNames.put("ASUS_Transformer_Pad_TF300T","Asus Transformer Pad");
|
||||
humanReadableNames.put("C5155","Kyocera Rise");
|
||||
humanReadableNames.put("C5170","Kyocera Hydro");
|
||||
humanReadableNames.put("C6603","Sony Xperia Z");
|
||||
humanReadableNames.put("Desire_HD","HTC Desire HD");
|
||||
humanReadableNames.put("DROID2_GLOBAL","Motorola Droid 2 Global");
|
||||
humanReadableNames.put("DROID2","Motorola Droid 2");
|
||||
humanReadableNames.put("DROID3","Motorola Droid 3");
|
||||
humanReadableNames.put("DROID4","Motorola Droid 4");
|
||||
humanReadableNames.put("DROID_BIONIC","Motorola Droid Bionic");
|
||||
humanReadableNames.put("Droid","Motorola Droid");
|
||||
humanReadableNames.put("DROID_Pro","Motorola Droid Pro");
|
||||
humanReadableNames.put("DROID_RAZR_HD","Motorola Droid Razr HD");
|
||||
humanReadableNames.put("DROID_RAZR","Motorola Droid Razr");
|
||||
humanReadableNames.put("DROID_X2","Motorola Droid X2");
|
||||
humanReadableNames.put("DROIDX","Motorola Droid X");
|
||||
humanReadableNames.put("EVO","HTC Evo");
|
||||
humanReadableNames.put("Galaxy_Nexus","Samsung Galaxy Nexus");
|
||||
humanReadableNames.put("google_sdk","Android Emulator");
|
||||
humanReadableNames.put("GT-I8160","Samsung Galaxy Ace 2");
|
||||
humanReadableNames.put("GT-I8190","Samsung Galaxy S III Mini");
|
||||
humanReadableNames.put("GT-I9000","Samsung Galaxy S");
|
||||
humanReadableNames.put("GT-I9001","Samsung Galaxy S Plus");
|
||||
humanReadableNames.put("GT-I9100M","Samsung Galaxy S II");
|
||||
humanReadableNames.put("GT-I9100P","Samsung Galaxy S II");
|
||||
humanReadableNames.put("GT-I9100","Samsung Galaxy S II");
|
||||
humanReadableNames.put("GT-I9100T","Samsung Galaxy S II");
|
||||
humanReadableNames.put("GT-I9300","Samsung Galaxy S III");
|
||||
humanReadableNames.put("GT-I9300T","Samsung Galaxy S III");
|
||||
humanReadableNames.put("GT-I9305","Samsung Galaxy S III");
|
||||
humanReadableNames.put("GT-I9505","Samsung Galaxy S 4");
|
||||
humanReadableNames.put("GT-N7000","Samsung Galaxy Note");
|
||||
humanReadableNames.put("GT-N7100","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("GT-N7105","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("GT-N8013","Samsung Galaxy Note 10.1");
|
||||
humanReadableNames.put("GT-P3113","Samsung Galaxy Tab 2 7.0");
|
||||
humanReadableNames.put("GT-P5113","Samsnung Galaxy Tab 2 10.1");
|
||||
humanReadableNames.put("GT-P7510","Samsung Galaxy Tab 10.1");
|
||||
humanReadableNames.put("GT-S5360","Samsung Galaxy Y");
|
||||
humanReadableNames.put("GT-S5570","Samsung Galaxy Mini");
|
||||
humanReadableNames.put("GT-S5830i","Samsung Galaxy Ace");
|
||||
humanReadableNames.put("GT-S5830","Samsung Galaxy Ace");
|
||||
humanReadableNames.put("HTC6435LVW","HTC Droid DNA");
|
||||
humanReadableNames.put("HTC_Desire_HD_A9191","HTC Desire HD");
|
||||
humanReadableNames.put("HTCEVODesign4G","HTC Evo Design 4G");
|
||||
humanReadableNames.put("HTCEVOV4G","HTC Evo V 4G");
|
||||
humanReadableNames.put("HTCONE","HTC One");
|
||||
humanReadableNames.put("HTC_PH39100","HTC Vivid 4G");
|
||||
humanReadableNames.put("HTC_Sensation_Z710e","HTC Sensation");
|
||||
humanReadableNames.put("HTC_VLE_U","HTC One S");
|
||||
humanReadableNames.put("KFJWA","Kindle Fire HD 8.9");
|
||||
humanReadableNames.put("KFJWI","Kindle Fire HD 8.9");
|
||||
humanReadableNames.put("KFOT","Kindle Fire");
|
||||
humanReadableNames.put("KFTT","Kindle Fire HD 7");
|
||||
humanReadableNames.put("LG-C800","LG myTouch Q");
|
||||
humanReadableNames.put("LG-E739","LG MyTouch e739");
|
||||
humanReadableNames.put("LGL55C","LG LGL55C");
|
||||
humanReadableNames.put("LG-LS970","LG Optimus G");
|
||||
humanReadableNames.put("LG-MS770","LG Motion 4G");
|
||||
humanReadableNames.put("LG-MS910","LG Esteem");
|
||||
humanReadableNames.put("LG-P509","LG Optimus T");
|
||||
humanReadableNames.put("LG-P769","LG Optimus L9");
|
||||
humanReadableNames.put("LG-P999","LG G2X P999");
|
||||
humanReadableNames.put("LG-VM696","LG Optimus Elite");
|
||||
humanReadableNames.put("LS670","LG Optimus S");
|
||||
humanReadableNames.put("LT26i","Sony Xperia S");
|
||||
humanReadableNames.put("MB855","Motorola Photon 4G");
|
||||
humanReadableNames.put("MB860","Motorola Atrix 4G");
|
||||
humanReadableNames.put("MB865","Motorola Atrix 2");
|
||||
humanReadableNames.put("MB886","Motorola Atrix HD");
|
||||
humanReadableNames.put("MOTWX435KT","Motorola Triumph");
|
||||
humanReadableNames.put("myTouch_4G_Slide","HTC myTouch 4G Slide");
|
||||
humanReadableNames.put("N860","ZTE Warp N860");
|
||||
humanReadableNames.put("Nexus_10","Google Nexus 10");
|
||||
humanReadableNames.put("Nexus_4","Google Nexus 4");
|
||||
humanReadableNames.put("Nexus_7","Asus Nexus 7");
|
||||
humanReadableNames.put("Nexus_S_4G","Samsung Nexus S 4G");
|
||||
humanReadableNames.put("Nexus_S","Samsung Nexus S");
|
||||
humanReadableNames.put("PantechP9070","Pantech Burst");
|
||||
humanReadableNames.put("PC36100","HTC Evo 4G");
|
||||
humanReadableNames.put("PG06100","HTC EVO Shift 4G");
|
||||
humanReadableNames.put("PG86100","HTC Evo 3D");
|
||||
humanReadableNames.put("PH44100","HTC Evo Design 4G");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I317","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I337","Samsung Galaxy S 4");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I717","Samsung Galaxy Note");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I727","Samsung Skyrocket");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I747","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I777","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I897","Samsung Captivate");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I927","Samsung Captivate Glide");
|
||||
humanReadableNames.put("SAMSUNG-SGH-I997","Samsung Infuse 4G");
|
||||
humanReadableNames.put("SCH-I200","Samsung Galaxy Stellar");
|
||||
humanReadableNames.put("SCH-I405","Samsung Stratosphere");
|
||||
humanReadableNames.put("SCH-I500","Samsung Fascinate");
|
||||
humanReadableNames.put("SCH-I510","Samsung Droid Charge");
|
||||
humanReadableNames.put("SCH-I535","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SCH-I545","Samsung Galaxy S 4");
|
||||
humanReadableNames.put("SCH-I605","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("SCH-I800","Samsung Galaxy Tab 7.0");
|
||||
humanReadableNames.put("SCH-R530M","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SCH-R530U","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SCH-R720","Samsung Admire");
|
||||
humanReadableNames.put("SCH-S720C","Samsung Proclaim");
|
||||
humanReadableNames.put("SGH-I317M","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("SGH-I727R","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SGH-I747M","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SGH-M919","Samsung Galaxy S 4");
|
||||
humanReadableNames.put("SGH-T679","Samsung Exhibit II");
|
||||
humanReadableNames.put("SGH-T769","Samsung Galaxy S Blaze");
|
||||
humanReadableNames.put("SGH-T889","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("SGH-T959","Samsung Galaxy S Vibrant");
|
||||
humanReadableNames.put("SGH-T959V","Samsung Galaxy S 4G");
|
||||
humanReadableNames.put("SGH-T989D","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SGH-T989","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SGH-T999","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SPH-D600","Samsung Conquer 4G");
|
||||
humanReadableNames.put("SPH-D700","Samsung Epic 4G");
|
||||
humanReadableNames.put("SPH-D710BST","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SPH-D710","Samsung Epic");
|
||||
humanReadableNames.put("SPH-D710VMUB","Samsung Galaxy S II");
|
||||
humanReadableNames.put("SPH-L710","Samsung Galaxy S III");
|
||||
humanReadableNames.put("SPH-L720","Samsung Galaxy S 4");
|
||||
humanReadableNames.put("SPH-L900","Samsung Galaxy Note II");
|
||||
humanReadableNames.put("SPH-M820-BST","Samsung Galaxy Prevail");
|
||||
humanReadableNames.put("SPH-M930BST","Samsung Transform Ultra");
|
||||
humanReadableNames.put("Transformer_Prime_TF201","Asus Eee Pad Transformer Prime");
|
||||
humanReadableNames.put("Transformer_TF101","Asus Eee Pad Transformer");
|
||||
humanReadableNames.put("VM670","LG Optimus V");
|
||||
humanReadableNames.put("VS840_4G","LG Lucid 4G");
|
||||
humanReadableNames.put("VS910_4G","LG Revolution 4G");
|
||||
humanReadableNames.put("VS920_4G","LG Spectrum 4G");
|
||||
humanReadableNames.put("Xoom","Motorola Xoom");
|
||||
humanReadableNames.put("XT907","Motorola Droid Razr M");
|
||||
}
|
||||
|
||||
static String getDeviceName() {
|
||||
|
||||
String dictName = humanReadableNames.get(Build.MODEL.replace(' ','_'));
|
||||
if (dictName != null) return dictName;
|
||||
|
||||
if (Build.BRAND.equals("samsung") || Build.BRAND.equals("Samsung")) {
|
||||
return "Samsung" + Build.MODEL;
|
||||
}
|
||||
|
||||
return Build.MODEL;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.util.Log;
|
||||
|
||||
public class KdeConnectBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
|
||||
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
//Log.e("KdeConnect", "Broadcast event: "+intent.getAction());
|
||||
|
||||
String action = intent.getAction();
|
||||
|
||||
if(action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
|
||||
Log.i("KdeConnect", "UpdateReceiver");
|
||||
if (!intent.getData().getSchemeSpecificPart().equals(context.getPackageName())) {
|
||||
Log.i("KdeConnect", "Ignoring, it's not me!");
|
||||
return;
|
||||
}
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
|
||||
}
|
||||
});
|
||||
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||
Log.i("KdeConnect", "KdeConnectBroadcastReceiver");
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
|
||||
}
|
||||
});
|
||||
} else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)
|
||||
|| action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)
|
||||
|| action.equals(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
) {
|
||||
Log.i("KdeConnect", "Connection state changed, trying to connect");
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.i("KdeConnectBroadcastReceiver", "Ignoring broadcast event: "+intent.getAction());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package org.kde.kdeconnect.LinkProviders;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class BaseLinkProvider {
|
||||
|
||||
private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
|
||||
|
||||
public interface ConnectionReceiver {
|
||||
public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link);
|
||||
public void onConnectionLost(BaseComputerLink link);
|
||||
}
|
||||
|
||||
public void addConnectionReceiver(ConnectionReceiver cr) {
|
||||
connectionReceivers.add(cr);
|
||||
}
|
||||
|
||||
public boolean removeConnectionReceiver(ConnectionReceiver cr) {
|
||||
return connectionReceivers.remove(cr);
|
||||
}
|
||||
|
||||
//These two should be called when the provider links to a new computer
|
||||
protected void connectionAccepted(NetworkPackage identityPackage, BaseComputerLink link) {
|
||||
Log.i("LinkProvider", "connectionAccepted");
|
||||
for(ConnectionReceiver cr : connectionReceivers) {
|
||||
cr.onConnectionReceived(identityPackage, link);
|
||||
}
|
||||
}
|
||||
protected void connectionLost(BaseComputerLink link) {
|
||||
Log.i("LinkProvider", "connectionLost");
|
||||
for(ConnectionReceiver cr : connectionReceivers) {
|
||||
cr.onConnectionLost(link);
|
||||
}
|
||||
}
|
||||
|
||||
//To override
|
||||
public abstract void onStart();
|
||||
public abstract void onStop();
|
||||
public abstract void onNetworkChange();
|
||||
|
||||
public abstract int getPriority();
|
||||
public abstract String getName();
|
||||
|
||||
}
|
@@ -0,0 +1,283 @@
|
||||
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.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 LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
private final static int port = 1714;
|
||||
|
||||
private Context context;
|
||||
private HashMap<String, LanComputerLink> visibleComputers = new HashMap<String, LanComputerLink>();
|
||||
private HashMap<Long, LanComputerLink> nioSessions = new HashMap<Long, LanComputerLink>();
|
||||
|
||||
private NioSocketAcceptor tcpAcceptor = null;
|
||||
private NioDatagramAcceptor udpAcceptor = null;
|
||||
|
||||
private final IoHandler tcpHandler = new IoHandlerAdapter() {
|
||||
@Override
|
||||
public void sessionClosed(IoSession session) throws Exception {
|
||||
|
||||
LanComputerLink brokenLink = nioSessions.remove(session.getId());
|
||||
if (brokenLink != null) {
|
||||
connectionLost(brokenLink);
|
||||
brokenLink.disconnect();
|
||||
String deviceId = brokenLink.getDeviceId();
|
||||
if (visibleComputers.get(deviceId) == brokenLink) {
|
||||
visibleComputers.remove(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(IoSession session, Object message) throws Exception {
|
||||
super.messageReceived(session, message);
|
||||
|
||||
//Log.e("LanLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString());
|
||||
|
||||
String theMessage = (String) message;
|
||||
NetworkPackage np = NetworkPackage.unserialize(theMessage);
|
||||
|
||||
LanComputerLink prevLink = nioSessions.get(session.getId());
|
||||
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
|
||||
if (np.getString("deviceId").equals(myId)) {
|
||||
return;
|
||||
}
|
||||
LanComputerLink link = new LanComputerLink(session, np.getString("deviceId"), LanLinkProvider.this);
|
||||
nioSessions.put(session.getId(),link);
|
||||
addLink(np, link);
|
||||
} else {
|
||||
if (prevLink == null) {
|
||||
Log.e("LanLinkProvider","2 Expecting an identity package");
|
||||
} else {
|
||||
prevLink.injectNetworkPackage(np);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private IoHandler udpHandler = new IoHandlerAdapter() {
|
||||
@Override
|
||||
public void messageReceived(IoSession udpSession, Object message) throws Exception {
|
||||
super.messageReceived(udpSession, message);
|
||||
|
||||
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
|
||||
|
||||
NetworkPackage np = null;
|
||||
|
||||
try {
|
||||
//We should receive a string thanks to the TextLineCodecFactory filter
|
||||
String theMessage = (String) message;
|
||||
np = NetworkPackage.unserialize(theMessage);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "Could not unserialize package");
|
||||
}
|
||||
|
||||
if (np != null) {
|
||||
|
||||
final NetworkPackage identityPackage = np;
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||
Log.e("LanLinkProvider", "1 Expecting an identity package");
|
||||
return;
|
||||
} else {
|
||||
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
|
||||
if (np.getString("deviceId").equals(myId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("LanLinkProvider", "Identity package received, creating link");
|
||||
|
||||
try {
|
||||
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
|
||||
|
||||
final NioSocketConnector connector = new NioSocketConnector();
|
||||
connector.setHandler(tcpHandler);
|
||||
//TextLineCodecFactory will split incoming data delimited by the given string
|
||||
connector.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(
|
||||
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
|
||||
)
|
||||
);
|
||||
connector.getSessionConfig().setKeepAlive(true);
|
||||
|
||||
int tcpPort = np.getInt("tcpPort",port);
|
||||
ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
|
||||
future.addListener(new IoFutureListener<IoFuture>() {
|
||||
@Override
|
||||
public void operationComplete(IoFuture ioFuture) {
|
||||
IoSession session = ioFuture.getSession();
|
||||
|
||||
Log.i("LanLinkProvider", "Connection successful: " + session.isConnected());
|
||||
|
||||
LanComputerLink link = new LanComputerLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this);
|
||||
|
||||
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
|
||||
link.sendPackage(np2);
|
||||
|
||||
nioSessions.put(session.getId(), link);
|
||||
addLink(identityPackage, link);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("LanLinkProvider","Exception!!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void addLink(NetworkPackage identityPackage, LanComputerLink link) {
|
||||
String deviceId = identityPackage.getString("deviceId");
|
||||
Log.i("LanLinkProvider","addLink to "+deviceId);
|
||||
LanComputerLink oldLink = visibleComputers.get(deviceId);
|
||||
visibleComputers.put(deviceId, link);
|
||||
connectionAccepted(identityPackage, link);
|
||||
if (oldLink != null) {
|
||||
Log.i("LanLinkProvider","Removing old connection to same device");
|
||||
oldLink.disconnect();
|
||||
connectionLost(oldLink);
|
||||
}
|
||||
}
|
||||
|
||||
public LanLinkProvider(Context context) {
|
||||
|
||||
this.context = context;
|
||||
|
||||
//This handles the case when I'm the new device in the network and somebody answers my introduction package
|
||||
tcpAcceptor = new NioSocketAcceptor();
|
||||
tcpAcceptor.setHandler(tcpHandler);
|
||||
tcpAcceptor.getSessionConfig().setKeepAlive(true);
|
||||
tcpAcceptor.getSessionConfig().setReuseAddress(true);
|
||||
tcpAcceptor.setCloseOnDeactivation(false);
|
||||
//TextLineCodecFactory will split incoming data delimited by the given string
|
||||
tcpAcceptor.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(
|
||||
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
udpAcceptor = new NioDatagramAcceptor();
|
||||
udpAcceptor.getSessionConfig().setReuseAddress(true); //Share port if existing
|
||||
//TextLineCodecFactory will split incoming data delimited by the given string
|
||||
udpAcceptor.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(
|
||||
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
|
||||
//This handles the case when I'm the existing device in the network and receive a "hello" UDP package
|
||||
|
||||
udpAcceptor.setHandler(udpHandler);
|
||||
|
||||
try {
|
||||
udpAcceptor.bind(new InetSocketAddress(port));
|
||||
} catch(Exception e) {
|
||||
Log.e("LanLinkProvider", "Error: Could not bind udp socket");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
int tcpPort = port;
|
||||
while(!success) {
|
||||
try {
|
||||
tcpAcceptor.bind(new InetSocketAddress(tcpPort));
|
||||
success = true;
|
||||
} catch(Exception e) {
|
||||
tcpPort++;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("LanLinkProvider","Using tcpPort "+tcpPort);
|
||||
|
||||
//I'm on a new network, let's be polite and introduce myself
|
||||
final int finalTcpPort = tcpPort;
|
||||
new AsyncTask<Void,Void,Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
|
||||
try {
|
||||
NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
|
||||
identity.set("tcpPort",finalTcpPort);
|
||||
byte[] b = identity.serialize().getBytes("UTF-8");
|
||||
DatagramPacket packet = new DatagramPacket(b, b.length, InetAddress.getByAddress(new byte[]{-1,-1,-1,-1}), port);
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
socket.setReuseAddress(true);
|
||||
socket.setBroadcast(true);
|
||||
socket.send(packet);
|
||||
//Log.e("LanLinkProvider","Udp identity package sent");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider","Sending udp identity package failed");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkChange() {
|
||||
|
||||
onStop();
|
||||
onStart();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
|
||||
udpAcceptor.unbind();
|
||||
tcpAcceptor.unbind();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LanLinkProvider";
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -30,13 +10,8 @@ import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
@@ -45,40 +20,29 @@ import javax.crypto.Cipher;
|
||||
|
||||
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_PAIR = "kdeconnect.pair";
|
||||
public final static String PACKAGE_TYPE_ENCRYPTED = "kdeconnect.encrypted";
|
||||
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
|
||||
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
|
||||
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
||||
public final static String PACKAGE_TYPE_SFTP = "kdeconnect.sftp";
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
|
||||
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
|
||||
public final static String PACKAGE_TYPE_MOUSEPAD = "kdeconnect.mousepad";
|
||||
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
|
||||
|
||||
private long mId;
|
||||
private String mType;
|
||||
private JSONObject mBody;
|
||||
private InputStream mPayload;
|
||||
private JSONObject mPayloadTransferInfo;
|
||||
private long mPayloadSize;
|
||||
|
||||
private NetworkPackage() {
|
||||
|
||||
}
|
||||
|
||||
public NetworkPackage(String type) {
|
||||
mId = System.currentTimeMillis();
|
||||
mType = type;
|
||||
mBody = new JSONObject();
|
||||
mPayload = null;
|
||||
mPayloadSize = 0;
|
||||
mPayloadTransferInfo = new JSONObject();
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
@@ -91,8 +55,6 @@ public class NetworkPackage {
|
||||
public void set(String key, String value) { if (value == null) return; try { mBody.put(key,value); } catch(Exception e) { } }
|
||||
public int getInt(String key) { return mBody.optInt(key,-1); }
|
||||
public int getInt(String key, int defaultValue) { return mBody.optInt(key,defaultValue); }
|
||||
public long getLong(String key) { return mBody.optLong(key,-1); }
|
||||
public long getLong(String key,long defaultValue) { return mBody.optLong(key,defaultValue); }
|
||||
public void set(String key, int value) { try { mBody.put(key,value); } catch(Exception e) { } }
|
||||
public boolean getBoolean(String key) { return mBody.optBoolean(key,false); }
|
||||
public boolean getBoolean(String key, boolean defaultValue) { return mBody.optBoolean(key,defaultValue); }
|
||||
@@ -100,9 +62,6 @@ public class NetworkPackage {
|
||||
public double getDouble(String key) { return mBody.optDouble(key,Double.NaN); }
|
||||
public double getDouble(String key, double defaultValue) { return mBody.optDouble(key,defaultValue); }
|
||||
public void set(String key, double value) { try { mBody.put(key,value); } catch(Exception e) { } }
|
||||
public JSONArray getJSONArray(String key) { return mBody.optJSONArray(key); }
|
||||
public void set(String key, JSONArray value) { try { mBody.put(key,value); } catch(Exception e) { } }
|
||||
|
||||
public ArrayList<String> getStringList(String key) {
|
||||
JSONArray jsonArray = mBody.optJSONArray(key);
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
@@ -132,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() {
|
||||
JSONObject jo = new JSONObject();
|
||||
try {
|
||||
jo.put("id", mId);
|
||||
jo.put("type", mType);
|
||||
jo.put("body", mBody);
|
||||
if (hasPayload()) {
|
||||
jo.put("payloadSize", mPayloadSize);
|
||||
jo.put("payloadTransferInfo", mPayloadTransferInfo);
|
||||
}
|
||||
jo.put("id",mId);
|
||||
jo.put("type",mType);
|
||||
jo.put("body",mBody);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPackage", "Serialization exception");
|
||||
}
|
||||
|
||||
//QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
|
||||
String json = jo.toString().replace("\\/","/")+"\n";
|
||||
|
||||
if (!isEncrypted()) {
|
||||
//Log.e("NetworkPackage.serialize", json);
|
||||
}
|
||||
|
||||
//Log.e("NetworkPackage.serialize",json);
|
||||
return json;
|
||||
}
|
||||
|
||||
static public NetworkPackage unserialize(String s) {
|
||||
|
||||
//Log.e("NetworkPackage.unserialize", s);
|
||||
NetworkPackage np = new NetworkPackage();
|
||||
try {
|
||||
JSONObject jo = new JSONObject(s);
|
||||
np.mId = jo.getLong("id");
|
||||
np.mType = jo.getString("type");
|
||||
np.mBody = jo.getJSONObject("body");
|
||||
if (jo.has("payloadSize")) {
|
||||
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
|
||||
np.mPayloadSize = jo.getLong("payloadSize");
|
||||
} else {
|
||||
np.mPayloadTransferInfo = new JSONObject();
|
||||
np.mPayloadSize = 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPackage", "Unserialization exception unserializing "+s);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!np.isEncrypted()) {
|
||||
//Log.e("NetworkPackage.unserialize", s);
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
public NetworkPackage encrypt(PublicKey publicKey) throws GeneralSecurityException {
|
||||
|
||||
|
||||
public void encrypt(PublicKey publicKey) throws Exception {
|
||||
|
||||
String serialized = serialize();
|
||||
|
||||
@@ -211,16 +146,21 @@ public class NetworkPackage {
|
||||
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);
|
||||
encrypted.set("data", chunks);
|
||||
encrypted.setPayload(mPayload, mPayloadSize);
|
||||
return encrypted;
|
||||
Log.i("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
|
||||
|
||||
}
|
||||
|
||||
public NetworkPackage decrypt(PrivateKey privateKey) throws GeneralSecurityException, JSONException {
|
||||
public NetworkPackage decrypt(PrivateKey privateKey) throws Exception {
|
||||
|
||||
JSONArray chunks = mBody.getJSONArray("data");
|
||||
|
||||
@@ -234,11 +174,10 @@ public class NetworkPackage {
|
||||
decryptedJson += decryptedChunk;
|
||||
}
|
||||
|
||||
NetworkPackage decrypted = unserialize(decryptedJson);
|
||||
decrypted.setPayload(mPayload, mPayloadSize);
|
||||
return decrypted;
|
||||
return unserialize(decryptedJson);
|
||||
}
|
||||
|
||||
|
||||
static public NetworkPackage createIdentityPackage(Context context) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
|
||||
@@ -246,22 +185,15 @@ public class NetworkPackage {
|
||||
String deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
try {
|
||||
np.mBody.put("deviceId", deviceId);
|
||||
np.mBody.put("deviceName",
|
||||
PreferenceManager.getDefaultSharedPreferences(context).getString(
|
||||
MainSettingsActivity.KEY_DEVICE_NAME_PREFERENCE,
|
||||
DeviceHelper.getDeviceName()));
|
||||
np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
|
||||
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
|
||||
np.mBody.put("deviceType", DeviceHelper.isTablet()? "tablet" : "phone");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPacakge","Exception on createIdentityPackage");
|
||||
} catch (JSONException e) {
|
||||
}
|
||||
|
||||
return np;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static public NetworkPackage createPublicKeyPackage(Context context) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||
@@ -276,40 +208,4 @@ public class NetworkPackage {
|
||||
|
||||
}
|
||||
|
||||
public void setPayload(byte[] data) {
|
||||
setPayload(new ByteArrayInputStream(data), data.length);
|
||||
}
|
||||
|
||||
public void setPayload(InputStream stream, long size) {
|
||||
mPayload = stream;
|
||||
mPayloadSize = size;
|
||||
}
|
||||
|
||||
/*public void setPayload(InputStream stream) {
|
||||
setPayload(stream, -1);
|
||||
}*/
|
||||
|
||||
public InputStream getPayload() {
|
||||
return mPayload;
|
||||
}
|
||||
|
||||
public long 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
package org.kde.kdeconnect.Plugins.BatteryPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.BatteryManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class BatteryPlugin extends Plugin {
|
||||
|
||||
private NetworkPackage lastPackage = null;
|
||||
|
||||
private IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
return "plugin_battery";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_battery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getResources().getString(R.string.pref_plugin_battery_desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getIcon() {
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
Log.i("BatteryPlugin", "Battery event");
|
||||
|
||||
boolean isCharging = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
|
||||
|
||||
int currentCharge = 100;
|
||||
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||||
if (status != BatteryManager.BATTERY_STATUS_FULL) {
|
||||
Intent batteryStatus = context.registerReceiver(null, filter);
|
||||
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||||
currentCharge = level*100 / scale;
|
||||
}
|
||||
|
||||
//Only notify if change is meaningful enough
|
||||
if (lastPackage == null
|
||||
|| (
|
||||
isCharging != lastPackage.getBoolean("isCharging")
|
||||
|| currentCharge != lastPackage.getInt("currentCharge")
|
||||
)
|
||||
) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
|
||||
np.set("isCharging", isCharging);
|
||||
np.set("currentCharge", currentCharge);
|
||||
device.sendPackage(np);
|
||||
lastPackage = np;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
context.registerReceiver(receiver, filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
context.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_BATTERY)) return false;
|
||||
|
||||
if (np.getBoolean("request")) {
|
||||
if (lastPackage != null) {
|
||||
device.sendPackage(lastPackage);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getInterfaceButton(Activity activity) {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.ClipboardManager;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
|
||||
public class ClipboardListener {
|
||||
|
||||
|
||||
private String currentContent;
|
||||
|
||||
private ClipboardManager cm = null;
|
||||
ClipboardManager.OnPrimaryClipChangedListener listener;
|
||||
|
||||
ClipboardListener(final Context context, final Device device) {
|
||||
cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
|
||||
@Override
|
||||
public void onPrimaryClipChanged() {
|
||||
try {
|
||||
|
||||
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
|
||||
String content = item.coerceToText(context).toString();
|
||||
|
||||
if (!content.equals(currentContent)) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CLIPBOARD);
|
||||
np.set("content", content);
|
||||
device.sendPackage(np);
|
||||
currentContent = content;
|
||||
}
|
||||
|
||||
} catch(Exception e) {
|
||||
//Probably clipboard was not text
|
||||
}
|
||||
}
|
||||
};
|
||||
cm.addPrimaryClipChangedListener(listener);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cm.removePrimaryClipChangedListener(listener);
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
currentContent = text;
|
||||
cm.setText(text);
|
||||
}
|
||||
|
||||
}
|
@@ -1,28 +1,11 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
@@ -31,6 +14,10 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class ClipboardPlugin extends Plugin {
|
||||
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(ClipboardPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
return "plugin_clipboard";
|
||||
@@ -50,28 +37,33 @@ public class ClipboardPlugin extends Plugin {
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
//Disabled by default due to just one direction sync(incoming clipboard change) in early version of android.
|
||||
return (android.os.Build.VERSION.SDK_INT >= 11);
|
||||
return (Build.VERSION.SDK_INT >= 11);
|
||||
}
|
||||
|
||||
private ClipboardListener listener;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
|
||||
if (Build.VERSION.SDK_INT < 11) {
|
||||
return false;
|
||||
}
|
||||
|
||||
listener = new ClipboardListener(context, device);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
if (Build.VERSION.SDK_INT < 11) return;
|
||||
|
||||
listener.stop();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,10 +76,22 @@ public class ClipboardPlugin extends Plugin {
|
||||
String content = np.getString("content");
|
||||
listener.setText(content);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Activity deviceActivity) { return null; }
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return new AlertDialog.Builder(baseContext)
|
||||
.setTitle(R.string.pref_plugin_clipboard)
|
||||
.setMessage(R.string.plugin_not_available)
|
||||
.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getInterfaceButton(Activity activity) {
|
@@ -0,0 +1,230 @@
|
||||
package org.kde.kdeconnect.Plugins.MprisPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MprisActivity extends Activity {
|
||||
|
||||
//TODO: Add a loading spinner at the begginning (to distinguish the loading state from a no-players state).
|
||||
//TODO 2: Add a message when no players are detected after loading completes
|
||||
|
||||
protected void connectToPlugin() {
|
||||
|
||||
final String deviceId = getIntent().getStringExtra("deviceId");
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
|
||||
Device device = service.getDevice(deviceId);
|
||||
final MprisPlugin mpris = (MprisPlugin) device.getPlugin("plugin_mpris");
|
||||
if (mpris == null) {
|
||||
Log.e("MprisActivity", "device has no mpris plugin!");
|
||||
return;
|
||||
}
|
||||
|
||||
mpris.setPlayerStatusUpdatedHandler(new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String s = mpris.getCurrentSong();
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText(s);
|
||||
|
||||
int volume = mpris.getVolume();
|
||||
((SeekBar) findViewById(R.id.volume_seek)).setProgress(volume);
|
||||
|
||||
boolean isPlaying = mpris.isPlaying();
|
||||
if (isPlaying) {
|
||||
((ImageButton) findViewById(R.id.play_button)).setImageResource(android.R.drawable.ic_media_pause);
|
||||
} else {
|
||||
((ImageButton) findViewById(R.id.play_button)).setImageResource(android.R.drawable.ic_media_play);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mpris.setPlayerListUpdatedHandler(new Handler() {
|
||||
boolean firstLoad = true;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final ArrayList<String> playerList = mpris.getPlayerList();
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(MprisActivity.this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
playerList.toArray(new String[playerList.size()])
|
||||
);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.player_spinner);
|
||||
//String prevPlayer = (String)spinner.getSelectedItem();
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> arg0, View arg1, int pos, long id) {
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText("");
|
||||
String player = playerList.get(pos);
|
||||
mpris.setPlayer(player);
|
||||
//Spotify doesn't support changing the volume yet...
|
||||
if (player.equals("Spotify")) {
|
||||
findViewById(R.id.volume_layout).setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
findViewById(R.id.volume_layout).setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (firstLoad) {
|
||||
firstLoad = false;
|
||||
if (playerList.size() > 0) {
|
||||
mpris.setPlayer(playerList.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
|
||||
@Override
|
||||
public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link) {
|
||||
connectToPlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionLost(BaseComputerLink link) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.removeConnectionListener(connectionReceiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.mpris_control);
|
||||
|
||||
final String deviceId = getIntent().getStringExtra("deviceId");
|
||||
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.addConnectionListener(connectionReceiver);
|
||||
}
|
||||
});
|
||||
connectToPlugin();
|
||||
|
||||
findViewById(R.id.play_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.sendAction("PlayPause");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.prev_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.sendAction("Previous");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.next_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.sendAction("Next");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
((SeekBar)findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) { }
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) { }
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
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.setVolume(seekBar.getProgress());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,27 +1,8 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.MprisPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
@@ -35,21 +16,23 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
public class MprisPlugin extends Plugin {
|
||||
|
||||
private String player = "";
|
||||
private boolean playing = false;
|
||||
private String currentSong = "";
|
||||
private int volume = 50;
|
||||
private long length = -1;
|
||||
private long lastPosition;
|
||||
private long lastPositionTime;
|
||||
private HashMap<String,Handler> playerStatusUpdated = new HashMap<String,Handler>();
|
||||
private Handler playerStatusUpdated = null;
|
||||
|
||||
private ArrayList<String> playerList = new ArrayList<String>();
|
||||
private HashMap<String,Handler> playerListUpdated = new HashMap<String,Handler>();
|
||||
private Handler playerListUpdated = null;
|
||||
|
||||
private String player = "";
|
||||
private boolean playing = false;
|
||||
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(MprisPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
@@ -71,11 +54,6 @@ public class MprisPlugin extends Plugin {
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
@@ -84,7 +62,6 @@ public class MprisPlugin extends Plugin {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
requestPlayerList();
|
||||
lastPositionTime = System.currentTimeMillis();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -93,60 +70,36 @@ public class MprisPlugin extends Plugin {
|
||||
playerList.clear();
|
||||
}
|
||||
|
||||
public void sendAction(String player, String action) {
|
||||
public void sendAction(String s) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("action", action);
|
||||
np.set("player",player);
|
||||
np.set("action",s);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
public void sendAction(String action) {
|
||||
sendAction(player, action);
|
||||
}
|
||||
|
||||
public void setVolume(int volume) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("player",player);
|
||||
np.set("setVolume",volume);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("SetPosition", position);
|
||||
device.sendPackage(np);
|
||||
this.lastPosition = position;
|
||||
this.lastPositionTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void Seek(int offset) {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player", player);
|
||||
np.set("Seek", offset);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_MPRIS)) return false;
|
||||
|
||||
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") || np.has("pos")) {
|
||||
if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying")) {
|
||||
if (np.getString("player").equals(player)) {
|
||||
currentSong = np.getString("nowPlaying", currentSong);
|
||||
volume = np.getInt("volume", volume);
|
||||
length = np.getLong("length", length);
|
||||
if(np.has("pos")){
|
||||
lastPosition = np.getLong("pos", lastPosition);
|
||||
lastPositionTime = System.currentTimeMillis();
|
||||
}
|
||||
playing = np.getBoolean("isPlaying", playing);
|
||||
for (String key : playerStatusUpdated.keySet()) {
|
||||
if (playerStatusUpdated != null) {
|
||||
try {
|
||||
playerStatusUpdated.get(key).dispatchMessage(new Message());
|
||||
playerStatusUpdated.dispatchMessage(new Message());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl","Exception");
|
||||
playerStatusUpdated.remove(key);
|
||||
playerStatusUpdated = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,13 +120,13 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
if (!equals) {
|
||||
playerList = newPlayerList;
|
||||
for (String key : playerListUpdated.keySet()) {
|
||||
if (playerListUpdated != null) {
|
||||
try {
|
||||
playerListUpdated.get(key).dispatchMessage(new Message());
|
||||
playerListUpdated.dispatchMessage(new Message());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl","Exception");
|
||||
playerListUpdated.remove(key);
|
||||
playerListUpdated = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,80 +135,58 @@ public class MprisPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setPlayerStatusUpdatedHandler(String id, Handler h) {
|
||||
playerStatusUpdated.put(id, h);
|
||||
if (playerListUpdated.size() == 1) {
|
||||
requestPlayerStatus();
|
||||
}
|
||||
|
||||
h.dispatchMessage(new Message());
|
||||
}
|
||||
|
||||
public void setPlayerListUpdatedHandler(String id, Handler h) {
|
||||
if (playerList.size() > 0) {
|
||||
h.dispatchMessage(new Message());
|
||||
}
|
||||
|
||||
playerListUpdated.put(id,h);
|
||||
if (playerListUpdated.size() == 1) {
|
||||
requestPlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlayer(String player) {
|
||||
if (player == null || player.equals(this.player)) return;
|
||||
this.player = player;
|
||||
currentSong = "";
|
||||
volume = 50;
|
||||
playing = false;
|
||||
for (String key : playerStatusUpdated.keySet()) {
|
||||
try {
|
||||
playerStatusUpdated.get(key).dispatchMessage(new Message());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl","Exception");
|
||||
playerStatusUpdated.remove(key);
|
||||
}
|
||||
}
|
||||
public void setPlayerStatusUpdatedHandler(Handler h) {
|
||||
playerStatusUpdated = h;
|
||||
if (currentSong.length() > 0) h.dispatchMessage(new Message());
|
||||
requestPlayerStatus();
|
||||
}
|
||||
|
||||
public ArrayList<String> getPlayerList() {
|
||||
return playerList;
|
||||
}
|
||||
|
||||
public String getCurrentSong() {
|
||||
return currentSong;
|
||||
}
|
||||
|
||||
public String getPlayer() {
|
||||
return player;
|
||||
public void setPlayerListUpdatedHandler(Handler h) {
|
||||
playerListUpdated = h;
|
||||
if (playerList.size() > 0) h.dispatchMessage(new Message());
|
||||
requestPlayerList();
|
||||
}
|
||||
|
||||
public ArrayList<String> getPlayerList() {
|
||||
return playerList;
|
||||
}
|
||||
|
||||
public void setPlayer(String s) {
|
||||
player = s;
|
||||
currentSong = "";
|
||||
volume = 50;
|
||||
playing = false;
|
||||
if (playerStatusUpdated != null) {
|
||||
try {
|
||||
playerStatusUpdated.dispatchMessage(new Message());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl","Exception");
|
||||
playerStatusUpdated = null;
|
||||
}
|
||||
}
|
||||
requestPlayerStatus();
|
||||
}
|
||||
|
||||
public int getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public long getLength(){ return length; }
|
||||
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
public long getPosition(){
|
||||
if(playing) {
|
||||
return lastPosition + (System.currentTimeMillis() - lastPositionTime);
|
||||
} else {
|
||||
return lastPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPlayerList() {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("requestPlayerList",true);
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
|
||||
private void requestPlayerStatus() {
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_MPRIS);
|
||||
np.set("player",player);
|
||||
@@ -265,7 +196,7 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Activity deviceActivity) {
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
||||
|
||||
import android.app.Service;
|
||||
@@ -25,10 +5,9 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class NotificationReceiver extends NotificationListenerService {
|
||||
|
||||
@@ -37,7 +16,7 @@ public class NotificationReceiver extends NotificationListenerService {
|
||||
void onNotificationRemoved(StatusBarNotification statusBarNotification);
|
||||
}
|
||||
|
||||
private final ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
|
||||
private ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
|
||||
|
||||
public void addListener(NotificationListener listener) {
|
||||
listeners.add(listener);
|
||||
@@ -48,7 +27,7 @@ public class NotificationReceiver extends NotificationListenerService {
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
|
||||
//Log.e("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
|
||||
Log.i("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
|
||||
for(NotificationListener listener : listeners) {
|
||||
listener.onNotificationPosted(statusBarNotification);
|
||||
}
|
||||
@@ -67,38 +46,29 @@ public class NotificationReceiver extends NotificationListenerService {
|
||||
|
||||
//To use the service from the outer (name)space
|
||||
|
||||
public interface InstanceCallback {
|
||||
void onServiceStart(NotificationReceiver service);
|
||||
}
|
||||
|
||||
private final static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
|
||||
|
||||
private final static Lock mutex = new ReentrantLock(true);
|
||||
|
||||
//This will be called for each intent launch, even if the service is already started and is reused
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
//Log.e("NotificationReceiver", "onStartCommand");
|
||||
mutex.lock();
|
||||
Log.i("NotificationReceiver", "onStartCommand");
|
||||
for (InstanceCallback c : callbacks) {
|
||||
c.onServiceStart(this);
|
||||
}
|
||||
callbacks.clear();
|
||||
mutex.unlock();
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
public interface InstanceCallback {
|
||||
void onServiceStart(NotificationReceiver service);
|
||||
}
|
||||
|
||||
private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
|
||||
|
||||
public static void Start(Context c) {
|
||||
RunCommand(c, null);
|
||||
}
|
||||
|
||||
public static void RunCommand(Context c, final InstanceCallback callback) {
|
||||
if (callback != null) {
|
||||
mutex.lock();
|
||||
callbacks.add(callback);
|
||||
mutex.unlock();
|
||||
}
|
||||
if (callback != null) callbacks.add(callback);
|
||||
Intent serviceIntent = new Intent(c, NotificationReceiver.class);
|
||||
c.startService(serviceIntent);
|
||||
}
|
@@ -1,36 +1,18 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
@@ -38,15 +20,16 @@ import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.Helpers.ImagesHelper;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.DeviceActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
|
||||
|
||||
private boolean sendIcons = false;
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(NotificationsPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
@@ -68,30 +51,13 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreferencesActivity(final SettingsActivity parentActivity) {
|
||||
if (hasPermission()) {
|
||||
Intent intent = new Intent(parentActivity, NotificationFilterActivity.class);
|
||||
parentActivity.startActivity(intent);
|
||||
} else {
|
||||
getErrorDialog(parentActivity).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasPermission() {
|
||||
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
|
||||
return (notificationListenerList != null && notificationListenerList.contains(context.getPackageName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class NotificationId {
|
||||
String packageName;
|
||||
@@ -150,24 +116,26 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return false;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 18) return false;
|
||||
|
||||
if (hasPermission()) {
|
||||
//Check for permissions
|
||||
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
|
||||
if (notificationListenerList != null && notificationListenerList.contains(context.getPackageName())) {
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
try {
|
||||
service.addListener(NotificationsPlugin.this);
|
||||
/*
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
onNotificationPosted(notification);
|
||||
}
|
||||
*/
|
||||
} catch(Exception e) {
|
||||
Log.e("NotificationsPlugin","Exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.e("NotificationsPlugin","Exception");
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@@ -180,11 +148,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
@@ -209,138 +172,59 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
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();
|
||||
AppDatabase appDatabase = new AppDatabase(context);
|
||||
|
||||
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
|
||||
|| (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0
|
||||
|| (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0) {
|
||||
//This is not a notification we want!
|
||||
return;
|
||||
}
|
||||
|
||||
appDatabase.open();
|
||||
if (!appDatabase.isEnabled(statusBarNotification.getPackageName())){
|
||||
return;
|
||||
// we dont want notification from this app
|
||||
}
|
||||
appDatabase.close();
|
||||
|
||||
NotificationId id = NotificationId.fromNotification(statusBarNotification);
|
||||
String packageName = statusBarNotification.getPackageName();
|
||||
String appName = AppsHelper.appNameLookup(context, packageName);
|
||||
|
||||
if (id.serialize().equals("com.facebook.orca::10012") && notification.tickerText == null && appName.equals("Messenger")) {
|
||||
//HACK: Hide weird Facebook empty "Messenger" notification that is actually not shown in the phone
|
||||
return;
|
||||
}
|
||||
|
||||
if (packageName.equals("com.google.android.googlequicksearchbox")) {
|
||||
//HACK: Hide Google Now notifications that keep constantly popping up (and without text because we don't know how to read them properly)
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
|
||||
|
||||
if (packageName.equals("org.kde.kdeconnect_tp"))
|
||||
{
|
||||
//Make our own notifications silent :)
|
||||
np.set("silent", true);
|
||||
np.set("requestAnswer", true); //For compatibility with old desktop versions of KDE Connect that don't support "silent"
|
||||
}
|
||||
String packageName = statusBarNotification.getPackageName();
|
||||
String appName = AppsHelper.appNameLookup(context, packageName);
|
||||
|
||||
if (sendIcons) {
|
||||
try {
|
||||
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
|
||||
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
if (appIcon.getWidth() > 128) {
|
||||
appIcon = Bitmap.createScaledBitmap(appIcon, 96, 96, true);
|
||||
}
|
||||
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
|
||||
byte[] bitmapData = outStream.toByteArray();
|
||||
np.setPayload(bitmapData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationsPlugin", "Error retrieving icon");
|
||||
}
|
||||
try {
|
||||
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
|
||||
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
|
||||
byte[] bitmapData = outStream.toByteArray();
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationsPlugin","Error retrieving icon");
|
||||
}
|
||||
|
||||
np.set("id", id.serialize());
|
||||
np.set("appName", appName == null? packageName : appName);
|
||||
np.set("isClearable", statusBarNotification.isClearable());
|
||||
np.set("ticker", getTickerText(notification));
|
||||
np.set("ticker", notification.tickerText.toString());
|
||||
np.set("time", Long.toString(statusBarNotification.getPostTime()));
|
||||
if (requestAnswer) {
|
||||
np.set("requestAnswer", true);
|
||||
np.set("silent", true);
|
||||
}
|
||||
if (requestAnswer) np.set("requestAnswer", true);
|
||||
|
||||
device.sendPackage(np);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ticker text of the notification.
|
||||
* If device android version is KitKat or newer, the title and text of the notification is used
|
||||
* instead the ticker text.
|
||||
*/
|
||||
private String getTickerText(Notification notification) {
|
||||
final String TITLE_KEY = "android.title";
|
||||
final String TEXT_KEY = "android.text";
|
||||
String ticker = "";
|
||||
|
||||
if(notification != null) {
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
Bundle extras = notification.extras;
|
||||
String extraTitle = extras.getString(TITLE_KEY);
|
||||
String extraText = null;
|
||||
Object extraTextExtra = extras.get(TEXT_KEY);
|
||||
if (extraTextExtra != null) extraText = extraTextExtra.toString();
|
||||
|
||||
if (extraTitle != null && extraText != null && !extraText.isEmpty()) {
|
||||
ticker = extraTitle + " ‐ " + extraText;
|
||||
} else if (extraTitle != null) {
|
||||
ticker = extraTitle;
|
||||
} else if (extraText != null) {
|
||||
ticker = extraText;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w("NotificationPlugin","problem parsing notification extras for " + notification.tickerText);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (ticker.isEmpty()) {
|
||||
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
|
||||
}
|
||||
}
|
||||
|
||||
return ticker;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(final NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
|
||||
|
||||
if (np.getBoolean("sendIcons")) {
|
||||
sendIcons = true;
|
||||
}
|
||||
|
||||
if (np.getBoolean("request")) {
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
private void sendCurrentNotifications(NotificationReceiver service) {
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification, true);
|
||||
onNotificationPosted(notification, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,10 +275,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(final Activity deviceActivity) {
|
||||
public AlertDialog getErrorDialog(final Context baseContext) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < 18) {
|
||||
return new AlertDialog.Builder(deviceActivity)
|
||||
return new AlertDialog.Builder(baseContext)
|
||||
.setTitle(R.string.pref_plugin_notifications)
|
||||
.setMessage(R.string.plugin_not_available)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@@ -405,14 +289,14 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
})
|
||||
.create();
|
||||
} else {
|
||||
return new AlertDialog.Builder(deviceActivity)
|
||||
return new AlertDialog.Builder(baseContext)
|
||||
.setTitle(R.string.pref_plugin_notifications)
|
||||
.setMessage(R.string.no_permissions)
|
||||
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
deviceActivity.startActivityForResult(intent, DeviceActivity.RESULT_NEEDS_RELOAD);
|
||||
baseContext.startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
|
@@ -1,46 +1,26 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.PingPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
|
||||
public class PingPlugin extends Plugin {
|
||||
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(PingPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
return "plugin_ping";
|
||||
@@ -61,11 +41,6 @@ public class PingPlugin extends Plugin {
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
@@ -88,36 +63,17 @@ public class PingPlugin extends Plugin {
|
||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
|
||||
//Log.e("PingPackageReceiver", "was a ping!");
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MainActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(context, MainActivity.class));
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
|
||||
0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
int id;
|
||||
String message;
|
||||
if (np.has("message")) {
|
||||
message = np.getString("message");
|
||||
id = (int)System.currentTimeMillis();
|
||||
} else {
|
||||
message = "Ping!";
|
||||
id = 42; //A unique id to create only one notification
|
||||
}
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(device.getName())
|
||||
.setContentText(message)
|
||||
.setContentIntent(resultPendingIntent)
|
||||
.setTicker(message)
|
||||
.setContentText("Ping!")
|
||||
.setTicker("Ping!")
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setDefaults(Notification.DEFAULT_SOUND)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(id, noti);
|
||||
notificationManager.notify(42 /*a unique id to create only one notification*/, noti);
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -125,7 +81,7 @@ public class PingPlugin extends Plugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Activity deviceActivity) {
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,43 +1,20 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
|
||||
public abstract class Plugin {
|
||||
|
||||
protected Device device;
|
||||
protected Context context;
|
||||
|
||||
public final void setContext(Context context, Device device) {
|
||||
public void setContext(Context context, Device device) {
|
||||
this.device = device;
|
||||
this.context = context;
|
||||
}
|
||||
@@ -74,24 +51,6 @@ public abstract class Plugin {
|
||||
*/
|
||||
public abstract boolean isEnabledByDefault();
|
||||
|
||||
/**
|
||||
* Return true if this plugin needs an specific UI settings.
|
||||
*/
|
||||
public abstract boolean hasSettings();
|
||||
|
||||
/**
|
||||
* If hasSettings returns true, this will be called when the user
|
||||
* wants to access this plugin preferences and should launch some
|
||||
* kind of interface. The default implementation will launch a
|
||||
* SettingsActivity with content from "yourplugin"_preferences.xml.
|
||||
*/
|
||||
public void startPreferencesActivity(SettingsActivity parentActivity) {
|
||||
Intent intent = new Intent(parentActivity, PluginSettingsActivity.class);
|
||||
intent.putExtra("plugin_display_name", getDisplayName());
|
||||
intent.putExtra("plugin_name", getPluginName());
|
||||
parentActivity.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the listeners and structures in your plugin.
|
||||
* Should return true if initialization was successful.
|
||||
@@ -114,7 +73,7 @@ public abstract class Plugin {
|
||||
* If onCreate returns false, should create a dialog explaining
|
||||
* the problem (and how to fix it, if possible) to the user.
|
||||
*/
|
||||
public abstract AlertDialog getErrorDialog(Activity deviceActivity);
|
||||
public abstract AlertDialog getErrorDialog(Context baseContext);
|
||||
|
||||
/**
|
||||
* Creates a button that will be displayed in the user interface
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins;
|
||||
|
||||
|
||||
@@ -27,13 +7,10 @@ import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
|
||||
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -44,14 +21,12 @@ public class PluginFactory {
|
||||
|
||||
public static class PluginInfo {
|
||||
|
||||
public PluginInfo(String pluginName, String displayName, String description, Drawable icon,
|
||||
boolean enabledByDefault, boolean hasSettings) {
|
||||
public PluginInfo(String pluginName, String displayName, String description, Drawable icon, boolean enabledByDefault) {
|
||||
this.pluginName = pluginName;
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
this.icon = icon;
|
||||
this.enabledByDefault = enabledByDefault;
|
||||
this.hasSettings = hasSettings;
|
||||
}
|
||||
|
||||
public String getPluginName() {
|
||||
@@ -70,18 +45,15 @@ public class PluginFactory {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public boolean hasSettings() { return hasSettings; }
|
||||
|
||||
public boolean isEnabledByDefault() {
|
||||
return enabledByDefault;
|
||||
}
|
||||
|
||||
private final String pluginName;
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
private String pluginName;
|
||||
private String displayName;
|
||||
private String description;
|
||||
private final Drawable icon;
|
||||
private final boolean enabledByDefault;
|
||||
private final boolean hasSettings;
|
||||
private boolean enabledByDefault;
|
||||
|
||||
}
|
||||
|
||||
@@ -89,16 +61,13 @@ public class PluginFactory {
|
||||
private static final Map<String, PluginInfo> availablePluginsInfo = new TreeMap<String, PluginInfo>();
|
||||
|
||||
static {
|
||||
//TODO: Use reflection to find all subclasses of Plugin, instead of adding them manually
|
||||
//TODO: Avoid this factory having to know every plugin
|
||||
PluginFactory.registerPlugin(TelephonyPlugin.class);
|
||||
PluginFactory.registerPlugin(PingPlugin.class);
|
||||
PluginFactory.registerPlugin(MprisPlugin.class);
|
||||
PluginFactory.registerPlugin(ClipboardPlugin.class);
|
||||
PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
PluginFactory.registerPlugin(SftpPlugin.class);
|
||||
//PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
PluginFactory.registerPlugin(NotificationsPlugin.class);
|
||||
PluginFactory.registerPlugin(MousePadPlugin.class);
|
||||
PluginFactory.registerPlugin(SharePlugin.class);
|
||||
}
|
||||
|
||||
public static PluginInfo getPluginInfo(Context context, String pluginName) {
|
||||
@@ -107,13 +76,12 @@ public class PluginFactory {
|
||||
try {
|
||||
Plugin p = ((Plugin)availablePlugins.get(pluginName).newInstance());
|
||||
p.setContext(context, null);
|
||||
info = new PluginInfo(pluginName, p.getDisplayName(), p.getDescription(), p.getIcon(),
|
||||
p.isEnabledByDefault(), p.hasSettings());
|
||||
info = new PluginInfo(pluginName, p.getDisplayName(), p.getDescription(), p.getIcon(), p.isEnabledByDefault());
|
||||
availablePluginsInfo.put(pluginName, info); //Cache it
|
||||
return info;
|
||||
} catch(Exception e) {
|
||||
Log.e("PluginFactory","getPluginInfo exception");
|
||||
e.printStackTrace();
|
||||
Log.e("PluginFactory","getPluginInfo exception");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -134,17 +102,17 @@ public class PluginFactory {
|
||||
plugin.setContext(context, device);
|
||||
return plugin;
|
||||
} catch(Exception e) {
|
||||
Log.e("PluginFactory", "Could not instantiate plugin: "+pluginName);
|
||||
e.printStackTrace();
|
||||
Log.e("PluginFactory", "Could not instantiate plugin: "+pluginName);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void registerPlugin(Class<? extends Plugin> pluginClass) {
|
||||
public static void registerPlugin(Class pluginClass) {
|
||||
try {
|
||||
//I hate this but I need to create an instance because abstract static functions can't be declared
|
||||
String pluginName = (pluginClass.newInstance()).getPluginName();
|
||||
String pluginName = ((Plugin)pluginClass.newInstance()).getPluginName();
|
||||
availablePlugins.put(pluginName, pluginClass);
|
||||
} catch(Exception e) {
|
||||
Log.e("PluginFactory","addPlugin exception");
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.TelephonyPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -27,10 +7,10 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.ContactsHelper;
|
||||
@@ -38,14 +18,11 @@ import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class TelephonyPlugin extends Plugin {
|
||||
|
||||
private int lastState = TelephonyManager.CALL_STATE_IDLE;
|
||||
private NetworkPackage lastPackage = null;
|
||||
private boolean isMuted = false;
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(TelephonyPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
@@ -72,12 +49,7 @@ public class TelephonyPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private final BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
private BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
@@ -113,12 +85,16 @@ public class TelephonyPlugin extends Plugin {
|
||||
callBroadcastReceived(finalIntState, finalNumber);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public void callBroadcastReceived(int state, String phoneNumber) {
|
||||
|
||||
//Log.e("TelephonyPlugin", "callBroadcastReceived");
|
||||
|
||||
private int lastState = TelephonyManager.CALL_STATE_IDLE;
|
||||
private NetworkPackage lastPackage = null;
|
||||
|
||||
public void callBroadcastReceived(int state, String phoneNumber) {
|
||||
|
||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY);
|
||||
if (phoneNumber != null) {
|
||||
@@ -128,11 +104,6 @@ public class TelephonyPlugin extends Plugin {
|
||||
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
if (isMuted) {
|
||||
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.setStreamMute(AudioManager.STREAM_RING, false);
|
||||
isMuted = false;
|
||||
}
|
||||
np.set("event", "ringing");
|
||||
device.sendPackage(np);
|
||||
break;
|
||||
@@ -150,20 +121,6 @@ public class TelephonyPlugin extends Plugin {
|
||||
lastPackage.set("isCancel","true");
|
||||
device.sendPackage(lastPackage);
|
||||
|
||||
if (isMuted) {
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isMuted) {
|
||||
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.setStreamMute(AudioManager.STREAM_RING, false);
|
||||
isMuted = false;
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
//Emit a missed call notification if needed
|
||||
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
|
||||
np.set("event","missedCall");
|
||||
@@ -205,7 +162,6 @@ public class TelephonyPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
//Log.e("TelephonyPlugin", "onCreate");
|
||||
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
|
||||
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
||||
context.registerReceiver(receiver, filter);
|
||||
@@ -219,23 +175,12 @@ public class TelephonyPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_TELEPHONY)) {
|
||||
return false;
|
||||
}
|
||||
if (np.getString("action").equals("mute")) {
|
||||
if (!isMuted) {
|
||||
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.setStreamMute(AudioManager.STREAM_RING, true);
|
||||
isMuted = true;
|
||||
}
|
||||
//Log.e("TelephonyPlugin", "mute");
|
||||
}
|
||||
//Do nothing
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Activity deviceActivity) {
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -0,0 +1,148 @@
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.List.ButtonItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DeviceActivity extends ActionBarActivity {
|
||||
|
||||
private String deviceId;
|
||||
private Device device;
|
||||
|
||||
private Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
|
||||
@Override
|
||||
public void onPluginsChanged(final Device device) {
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
//Errors list
|
||||
final HashMap<String, Plugin> failedPlugins = device.getFailedPlugins();
|
||||
final String[] ids = failedPlugins.keySet().toArray(new String[failedPlugins.size()]);
|
||||
String[] names = new String[failedPlugins.size()];
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
Plugin p = failedPlugins.get(ids[i]);
|
||||
names[i] = p.getDisplayName();
|
||||
}
|
||||
ListView errorList = (ListView)findViewById(R.id.errors_list);
|
||||
if (!failedPlugins.isEmpty() && errorList.getHeaderViewsCount() == 0) {
|
||||
TextView header = new TextView(DeviceActivity.this);
|
||||
header.setPadding(0,24,0,0);
|
||||
header.setText(getResources().getString(R.string.plugins_failed_to_load));
|
||||
errorList.addHeaderView(header);
|
||||
}
|
||||
errorList.setAdapter(new ArrayAdapter<String>(DeviceActivity.this, android.R.layout.simple_list_item_1, names));
|
||||
errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to substract one
|
||||
p.getErrorDialog(DeviceActivity.this).show();
|
||||
}
|
||||
});
|
||||
|
||||
//Buttons list
|
||||
ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
|
||||
final Collection<Plugin> plugins = device.getLoadedPlugins().values();
|
||||
for (Plugin p : plugins) {
|
||||
Button b = p.getInterfaceButton(DeviceActivity.this);
|
||||
if (b != null) {
|
||||
items.add(new SectionItem(p.getDisplayName()));
|
||||
items.add(new ButtonItem(b));
|
||||
}
|
||||
}
|
||||
ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
|
||||
buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_device);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
deviceId = getIntent().getStringExtra("deviceId");
|
||||
|
||||
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(deviceId);
|
||||
setTitle(device.getName());
|
||||
device.addPluginsChangedListener(pluginsChangedListener);
|
||||
pluginsChangedListener.onPluginsChanged(device);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
}
|
||||
});
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
menu.clear();
|
||||
if (device.isPaired()) {
|
||||
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
|
||||
intent.putExtra("deviceId", deviceId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
device.unpair();
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package org.kde.kdeconnect.UserInterface.List;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
public class ButtonItem implements ListAdapter.Item {
|
||||
|
||||
private final Button button;
|
||||
|
||||
public ButtonItem(Button b) {
|
||||
this.button = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View inflateView(LayoutInflater layoutInflater) {
|
||||
return button;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package org.kde.kdeconnect.UserInterface.List;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.DeviceActivity;
|
||||
import org.kde.kdeconnect.UserInterface.PairActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class DeviceItem implements ListAdapter.Item {
|
||||
|
||||
private final Device device;
|
||||
private final Activity activity;
|
||||
|
||||
public DeviceItem(Activity activity, Device device) {
|
||||
this.device = device;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@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(device.getName());
|
||||
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent;
|
||||
if (device.isPaired()) {
|
||||
intent = new Intent(activity, DeviceActivity.class);
|
||||
} else {
|
||||
intent = new Intent(activity, PairActivity.class);
|
||||
}
|
||||
intent.putExtra("deviceId", device.getDeviceId());
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package org.kde.kdeconnect.UserInterface.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ListAdapter extends ArrayAdapter<ListAdapter.Item> {
|
||||
|
||||
public interface Item {
|
||||
public View inflateView(LayoutInflater layoutInflater);
|
||||
}
|
||||
|
||||
private ArrayList<Item> items;
|
||||
private LayoutInflater layoutInflater;
|
||||
|
||||
public ListAdapter(Context context, ArrayList<Item> items) {
|
||||
super(context, 0, items);
|
||||
this.items = items;
|
||||
layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final Item i = items.get(position);
|
||||
return i.inflateView(layoutInflater);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
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 SectionItem implements ListAdapter.Item {
|
||||
|
||||
private final String title;
|
||||
public boolean isEmpty;
|
||||
|
||||
public SectionItem(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View inflateView(LayoutInflater layoutInflater) {
|
||||
|
||||
View v = layoutInflater.inflate(R.layout.list_item_category, null);
|
||||
|
||||
v.setOnClickListener(null);
|
||||
v.setOnLongClickListener(null);
|
||||
v.setLongClickable(false);
|
||||
|
||||
TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text);
|
||||
sectionView.setText(title);
|
||||
|
||||
if (isEmpty) {
|
||||
v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
}
|
@@ -1,31 +1,11 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -86,12 +66,6 @@ public class MainActivity extends ActionBarActivity {
|
||||
}
|
||||
}).start();
|
||||
break;
|
||||
case R.id.menu_settings:
|
||||
startActivity(new Intent(this,MainSettingsActivity.class));
|
||||
break;
|
||||
case R.id.menu_custom_device_list:
|
||||
startActivity(new Intent(this, CustomDevicesActivity.class));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -105,6 +79,7 @@ public class MainActivity extends ActionBarActivity {
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -127,36 +102,36 @@ public class MainActivity extends ActionBarActivity {
|
||||
Resources res = getResources();
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_connected_devices));
|
||||
section.isSectionEmpty = true;
|
||||
section.isEmpty = true;
|
||||
items.add(section);
|
||||
for(Device d : devices) {
|
||||
if (d.isReachable() && d.isPaired()) {
|
||||
items.add(new DeviceItem(MainActivity.this, d));
|
||||
section.isSectionEmpty = false;
|
||||
section.isEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
||||
section.isSectionEmpty = true;
|
||||
section.isEmpty = true;
|
||||
items.add(section);
|
||||
for(Device d : devices) {
|
||||
if (d.isReachable() && !d.isPaired()) {
|
||||
items.add(new DeviceItem(MainActivity.this, d));
|
||||
section.isSectionEmpty = false;
|
||||
section.isEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_remembered_devices));
|
||||
section.isSectionEmpty = true;
|
||||
section.isEmpty = true;
|
||||
items.add(section);
|
||||
for(Device d : devices) {
|
||||
if (!d.isReachable() && d.isPaired()) {
|
||||
items.add(new DeviceItem(MainActivity.this, d));
|
||||
section.isSectionEmpty = false;
|
||||
section.isEmpty = false;
|
||||
}
|
||||
}
|
||||
if (section.isSectionEmpty) {
|
||||
items.remove(items.size()-1); //Remove remembered devices section if empty
|
||||
if (section.isEmpty) {
|
||||
items.remove(items.size()-1); //Remove section
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -167,13 +142,12 @@ public class MainActivity extends ActionBarActivity {
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
view.performClick();
|
||||
view.callOnClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
@@ -38,7 +18,7 @@ public class PairActivity extends ActionBarActivity {
|
||||
private String deviceId;
|
||||
private Device device = null;
|
||||
|
||||
private final Device.PairingCallback pairingCallback = new Device.PairingCallback() {
|
||||
private Device.PairingCallback pairingCallback = new Device.PairingCallback() {
|
||||
|
||||
@Override
|
||||
public void incomingRequest() {
|
||||
@@ -96,7 +76,6 @@ public class PairActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
setTitle(device.getName());
|
||||
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(device.getNotificationId());
|
||||
@@ -115,7 +94,6 @@ public class PairActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.requestPairing();
|
||||
}
|
||||
});
|
||||
@@ -156,8 +134,6 @@ public class PairActivity extends ActionBarActivity {
|
||||
BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.addPairingCallback(pairingCallback);
|
||||
}
|
||||
});
|
||||
@@ -169,4 +145,4 @@ public class PairActivity extends ActionBarActivity {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PreferenceListAdapter extends ArrayAdapter<Preference> {
|
||||
|
||||
|
||||
private ArrayList<Preference> localList;
|
||||
|
||||
public PreferenceListAdapter(Context context, ArrayList<Preference> items) {
|
||||
super(context,0, items);
|
||||
localList = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
Preference preference = localList.get(position);
|
||||
return preference.getView(convertView, parent);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
public class SettingsActivity extends ListActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final String deviceId = getIntent().getStringExtra("deviceId");
|
||||
BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
Set<String> plugins = PluginFactory.getAvailablePlugins();
|
||||
|
||||
final ArrayList<Preference> preferences = new ArrayList<Preference>();
|
||||
for (final String pluginName : plugins) {
|
||||
CheckBoxPreference pref = new CheckBoxPreference(getBaseContext());
|
||||
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(getBaseContext(), pluginName);
|
||||
pref.setKey(pluginName);
|
||||
pref.setTitle(info.getDisplayName());
|
||||
pref.setSummary(info.getDescription());
|
||||
pref.setChecked(device.isPluginEnabled(pluginName));
|
||||
preferences.add(pref);
|
||||
}
|
||||
|
||||
setListAdapter(new PreferenceListAdapter(SettingsActivity.this, preferences));
|
||||
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
|
||||
CheckBoxPreference pref = (CheckBoxPreference)preferences.get(i);
|
||||
|
||||
boolean enabled = device.isPluginEnabled(pref.getKey());
|
||||
device.setPluginEnabled(pref.getKey(), !enabled);
|
||||
|
||||
pref.setChecked(!enabled);
|
||||
|
||||
getListAdapter().getView(i, view, null); //This will refresh the view (yes, this is the way to do it)
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
getListView().setPadding(16,16,16,16);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
BIN
KdeConnect/src/main/res/drawable/action_settings.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
KdeConnect/src/main/res/drawable/icon.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
KdeConnect/src/main/res/drawable/navigation_refresh.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
KdeConnect/src/main/res/drawable/volume.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
26
KdeConnect/src/main/res/layout/activity_device.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".MainActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/buttons_list"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/errors_list"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
48
KdeConnect/src/main/res/layout/list_item_entry.xml
Normal 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>
|
110
KdeConnect/src/main/res/layout/mpris_control.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/mpris_control_view"
|
||||
android:gravity="center">
|
||||
|
||||
<Spinner
|
||||
android:layout_width="200dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/player_spinner"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/now_playing_textview"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:padding="5dip"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="200dip"
|
||||
android:layout_height="75dip"
|
||||
android:id="@+id/play_button"
|
||||
android:src="@android:drawable/ic_media_play"
|
||||
android:contentDescription="@string/mpris_play"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="200dip"
|
||||
android:layout_height="70dip"
|
||||
android:layout_gravity="center"
|
||||
>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/prev_button"
|
||||
android:src="@android:drawable/ic_media_rew"
|
||||
android:contentDescription="@string/mpris_previous"
|
||||
android:layout_weight="0.5"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/next_button"
|
||||
android:src="@android:drawable/ic_media_ff"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/mpris_next"
|
||||
android:layout_weight="0.5"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="200dip"
|
||||
android:layout_height="70dip"
|
||||
android:id="@+id/volume_layout"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="50dip"
|
||||
android:layout_height="50dip"
|
||||
android:layout_marginRight="10dip"
|
||||
android:id="@+id/imageView"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:contentDescription="@string/mpris_volume"
|
||||
android:src="@drawable/volume"
|
||||
/>
|
||||
|
||||
|
||||
<SeekBar
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/volume_seek"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!--
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pause_button"
|
||||
android:src="@android:drawable/ic_media_pause"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pause_button"
|
||||
android:src="@android:drawable/ic_media_stop"
|
||||
android:layout_gravity="center"/>
|
||||
-->
|
||||
</LinearLayout>
|
21
KdeConnect/src/main/res/menu/main.xml
Normal 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>
|
4
KdeConnect/src/main/res/values-sw600dp/dimens.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<!-- Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw600dp devices (e.g. 7" tablets) here. -->
|
||||
</resources>
|
5
KdeConnect/src/main/res/values-sw720dp-land/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. -->
|
||||
<dimen name="activity_horizontal_margin">128dp</dimen>
|
||||
</resources>
|
56
KdeConnect/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="pref_plugin_telephony">Telephony notifier</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_desc">Periodically report battery status</string>
|
||||
<string name="pref_plugin_clipboard">Clipboard sync</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_desc">Control audio/video from your phone</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send and receive pings</string>
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
<string name="pref_plugin_notifications_desc">Access your notifications from other devices</string>
|
||||
<string name="plugin_not_available">This feature is not available in your Android version</string>
|
||||
<string name="device_list_empty">No devices</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="no_permissions">You need to grant permission to access notifications</string>
|
||||
<string name="send_ping">Send ping</string>
|
||||
<string name="open_mpris_controls">Open remote control</string>
|
||||
<string name="category_connected_devices">Connected devices</string>
|
||||
<string name="category_not_paired_devices">Not paired devices</string>
|
||||
<string name="category_remembered_devices">Remembered devices</string>
|
||||
<string name="plugins_failed_to_load">Plugins failed to load (tap for more info):</string>
|
||||
<string name="device_menu_plugins">Select plugins</string>
|
||||
<string name="device_menu_unpair">Unpair</string>
|
||||
<string name="unknown_device">Unknown device</string>
|
||||
<string name="error_not_reachable">Device not reachable</string>
|
||||
<string name="error_already_requested">Pairing already requested</string>
|
||||
<string name="error_already_paired">Device already paired</string>
|
||||
<string name="error_could_not_send_package">Could not send package</string>
|
||||
<string name="error_timed_out">Timed out</string>
|
||||
<string name="error_canceled_by_user">Canceled by user</string>
|
||||
<string name="error_canceled_by_other_peer">Canceled by other peer</string>
|
||||
<string name="error_invalid_key">Invalid key received</string>
|
||||
<string name="pair_requested">Pair requested</string>
|
||||
<string name="pairing_request_from">Pairing request from %1s</string>
|
||||
<string name="tap_to_answer">Tap to answer</string>
|
||||
<string name="reconnect">Reconnect</string>
|
||||
<string name="device_not_paired">Device not paired</string>
|
||||
<string name="request_pairing">Request pairing</string>
|
||||
<string name="pairing_accept">Accept</string>
|
||||
<string name="pairing_reject">Reject</string>
|
||||
<string name="device">Device</string>
|
||||
<string name="pair_device">Pair device</string>
|
||||
<string name="remote_control">Remote control</string>
|
||||
<string name="settings">KDE Connect Settings</string>
|
||||
<string name="mpris_play">Play</string>
|
||||
<string name="mpris_previous">Previous</string>
|
||||
<string name="mpris_next">Next</string>
|
||||
<string name="mpris_volume">Volume</string>
|
||||
|
||||
</resources>
|
@@ -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 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 --ignore-fuzzy --android res/ --gettext $podir
|
||||
}
|
||||
|
||||
|
54
build.gradle
@@ -1,54 +1,2 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.0.+'
|
||||
}
|
||||
}
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion '21.1.2'
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 21
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['resources']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
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
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
buildTypes {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
compile 'com.android.support:support-v4:21.0.3'
|
||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||
compile 'org.apache.mina:mina-core:2.0.9'
|
||||
compile 'org.apache.sshd:sshd-core:0.8.0'
|
||||
compile 'org.bouncycastle:bcprov-jdk16:1.46'
|
||||
|
||||
//compile fileTree(dir: 'libs', include: '*.jar')
|
||||
}
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
|
164
gradlew
vendored
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
1067
icon.svg
Before Width: | Height: | Size: 35 KiB |
13
kdeconnect-android.iml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?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="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
Before Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 557 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 451 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 713 B |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 468 B |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 750 B |
Before Width: | Height: | Size: 365 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,13 +0,0 @@
|
||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/buttons_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:fillViewport="true"
|
||||
android:divider="@null"
|
||||
tools:context=".MainActivity"
|
||||
/>
|
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:keepScreenOn="true"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/mousepad_view">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/mousepad_info"
|
||||
android:gravity="center"
|
||||
style="@android:style/TextAppearance.Medium"
|
||||
android:layout_centerInParent="true"
|
||||
android:padding="12dip" />
|
||||
|
||||
<view
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
class="org.kde.kdeconnect.Plugins.MousePadPlugin.KeyListenerView"
|
||||
android:id="@+id/keyListener" />
|
||||
|
||||
</RelativeLayout>
|
@@ -1,22 +0,0 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationFilterActivity">
|
||||
|
||||
<TextView
|
||||
android:text="@string/filter_apps_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:id="@+id/tFilter"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/lvFilterApps"
|
||||
android:layout_below="@id/tFilter"/>
|
||||
|
||||
</RelativeLayout>
|
@@ -1,37 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@android:id/list"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="100dp"
|
||||
android:text="@string/custom_dev_list_help"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionSend"
|
||||
android:hint="@string/add_host_hint"
|
||||
android:id="@+id/ip_edittext"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/add_host"
|
||||
android:onClick="addNewIp"
|
||||
android:id="@android:id/button1"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
@@ -1,35 +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:paddingLeft="12dip"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize"
|
||||
android:background="@drawable/abc_list_selector_holo_dark"
|
||||
android:orientation="vertical">
|
||||
<!-- We should use android:background="@android:/listChoiceBackgroundIndicator"
|
||||
instead of abc_list_selector_holo_dark but that's only supported from API11 -->
|
||||
|
||||
<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>
|
@@ -1,182 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/mpris_control_view"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="60dip"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingRight="60dip"
|
||||
android:paddingBottom="5dip">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/no_players_connected"
|
||||
android:id="@+id/no_players"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<Spinner
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/player_spinner"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/now_playing_textview"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:padding="8dip"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
<org.kde.kdeconnect.UserInterface.MaxWidthImageButton
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="75dip"
|
||||
app:maxWidth="300dip"
|
||||
android:id="@+id/play_button"
|
||||
android:src="@android:drawable/ic_media_play"
|
||||
android:contentDescription="@string/mpris_play"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:clickable="false"
|
||||
android:adjustViewBounds="false"
|
||||
android:baselineAlignBottom="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dip"
|
||||
android:layout_gravity="center"
|
||||
>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
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:contentDescription="@string/mpris_rew"
|
||||
android:layout_weight="0.25"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/ff_button"
|
||||
android:src="@android:drawable/ic_media_ff"
|
||||
android:contentDescription="@string/mpris_ff"
|
||||
android:layout_weight="0.25"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/next_button"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:contentDescription="@string/mpris_next"
|
||||
android:layout_weight="0.25"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/progress_slider">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/progress_textview"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:padding="8dip"
|
||||
android:layout_gravity="center"
|
||||
android:text="00:00" />
|
||||
|
||||
<SeekBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/positionSeek"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/time_textview"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:padding="8dip"
|
||||
android:layout_gravity="center"
|
||||
android:text="00:00" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/volume_layout"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dip">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dip"
|
||||
android:layout_height="30dip"
|
||||
android:maxWidth="30dip"
|
||||
android:layout_marginRight="10dip"
|
||||
android:id="@+id/imageView"
|
||||
android:layout_weight="0"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:contentDescription="@string/mpris_volume"
|
||||
android:src="@drawable/ic_volume"
|
||||
/>
|
||||
|
||||
|
||||
<SeekBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/volume_seek"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!--
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pause_button"
|
||||
android:src="@android:drawable/ic_media_pause"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pause_button"
|
||||
android:src="@android:drawable/ic_media_stop"
|
||||
android:layout_gravity="center"/>
|
||||
-->
|
||||
</LinearLayout>
|
@@ -1,36 +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"
|
||||
/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_custom_device_list"
|
||||
android:icon="@drawable/ic_action_settings"
|
||||
android:orderInCategory="900"
|
||||
android:title="@string/custom_device_list"
|
||||
kdeconnect:showAsAction="never"
|
||||
/>
|
||||
|
||||
</menu>
|
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto/android">
|
||||
|
||||
<item android:id="@+id/menu_show_keyboard"
|
||||
android:title="@string/show_keyboard"
|
||||
android:icon="@drawable/ic_action_keyboard"
|
||||
kdeconnect:showAsAction="ifRoom" />
|
||||
|
||||
<item android:id="@+id/menu_right_click"
|
||||
android:title="@string/right_click"
|
||||
kdeconnect:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/menu_middle_click"
|
||||
android:title="@string/middle_click"
|
||||
kdeconnect:showAsAction="never" />
|
||||
|
||||
</menu>
|
@@ -1,109 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="pref_plugin_telephony">مُخطِر المهاتفة</string>
|
||||
<string name="pref_plugin_telephony_desc">أرسل إخطارات لرسائل SMS والمكالمات</string>
|
||||
<string name="pref_plugin_battery">تقرير البطّاريّة</string>
|
||||
<string name="pref_plugin_battery_desc">بلّغ عن حالة البطّاريّة دوريًّا</string>
|
||||
<string name="pref_plugin_sftp">اكشف نظام الملفّات</string>
|
||||
<string name="pref_plugin_sftp_desc">تسمح بتصفّح نظام ملفّات الهاتف عن بعد</string>
|
||||
<string name="pref_plugin_clipboard">مزامنة الحافظة</string>
|
||||
<string name="pref_plugin_clipboard_desc">شارك محتوى الحافظة</string>
|
||||
<string name="pref_plugin_mousepad">الدَّخل البعيد</string>
|
||||
<string name="pref_plugin_mousepad_desc">استخدم هاتفك كفأرة ولوحة مفاتيح</string>
|
||||
<string name="pref_plugin_mpris">تحكّمات الوسائط المتعدّدة البعيدة</string>
|
||||
<string name="pref_plugin_mpris_desc">تحكّم بالصّوت والصّورة من هاتفك</string>
|
||||
<string name="pref_plugin_ping">وخزة</string>
|
||||
<string name="pref_plugin_ping_desc">أرسل واستقبل وخزات</string>
|
||||
<string name="pref_plugin_notifications">مزامنة الإخطارات</string>
|
||||
<string name="pref_plugin_notifications_desc">انفذ إلى إخطاراتك من أجهزة أخرى</string>
|
||||
<string name="pref_plugin_sharereceiver">شارك المستقبل</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">احفظ الملفّات الواردة إلى تخزين الهاتف</string>
|
||||
<string name="plugin_not_available">هذه الميزة غير متوفّرة في إصدار أندرويد لديك</string>
|
||||
<string name="device_list_empty">لا أجهزة</string>
|
||||
<string name="ok">حسنًا</string>
|
||||
<string name="cancel">ألغِ</string>
|
||||
<string name="open_settings">افتح الإعدادات</string>
|
||||
<string name="no_permissions">عليك إعطاء التّطبيق صلاحيّات للنّفاذ إلى الإخطارات</string>
|
||||
<string name="send_ping">أرسل وخزة</string>
|
||||
<string name="open_mpris_controls">افتح التّحكّم البعيد</string>
|
||||
<string name="open_mousepad">افتح تحكّم لوحة اللمس</string>
|
||||
<string-array name="mousepad_tap_entries">
|
||||
<item/>
|
||||
<item/>
|
||||
<item>Nothing</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">الأجهزة المقترن بها</string>
|
||||
<string name="category_not_paired_devices">الأجهزة المتوفّرة</string>
|
||||
<string name="category_remembered_devices">الأجهزة المتذكَّرة</string>
|
||||
<string name="plugins_failed_to_load">فشل تحميل الملحقات (المس لمعلومات اكثر):</string>
|
||||
<string name="device_menu_unpair">أزل الاقتران</string>
|
||||
<string name="device_not_reachable">الجهاز المقترن غير قابل الوصول</string>
|
||||
<string name="unknown_device">جهاز مجهول</string>
|
||||
<string name="error_not_reachable">الجهاز غير قابل الوصول</string>
|
||||
<string name="error_already_requested">طُلب الاقتران بالفعل</string>
|
||||
<string name="error_already_paired">الجهاز مقترن بالفعل</string>
|
||||
<string name="error_could_not_send_package">تعذّر إرسال الحزمة</string>
|
||||
<string name="error_timed_out">انتهت المهلة</string>
|
||||
<string name="error_canceled_by_user">ألغاه المستخدم</string>
|
||||
<string name="error_canceled_by_other_peer">ألغاه ندّ آخر</string>
|
||||
<string name="error_invalid_key">استُقبل مفتاح غير صالح</string>
|
||||
<string name="pair_requested">طُلب الاقتران</string>
|
||||
<string name="pairing_request_from">طلب اقتران من %1s</string>
|
||||
<string name="received_url_title">استُلمت وصلة من %1s</string>
|
||||
<string name="received_url_text">المس لفتح \'%1s\'</string>
|
||||
<string name="incoming_file_title">ملفّ وارد من %1s</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="received_file_title">استُلم ملفّ من %1s</string>
|
||||
<string name="received_file_text">المس لفتح \'%1s\'</string>
|
||||
<string name="tap_to_answer">المس للإجابة</string>
|
||||
<string name="reconnect">أعد الاتّصال</string>
|
||||
<string name="right_click">أرسل نقرة باليمين</string>
|
||||
<string name="middle_click">أرسل نقرة بالوسط</string>
|
||||
<string name="show_keyboard">أظهر لوحة المفاتيح</string>
|
||||
<string name="device_not_paired">الجهاز غير مقترن</string>
|
||||
<string name="request_pairing">اطلب الاقتران</string>
|
||||
<string name="pairing_accept">اقبل</string>
|
||||
<string name="pairing_reject">ارفض</string>
|
||||
<string name="device">الجهاز</string>
|
||||
<string name="pair_device">اقرن الجهاز</string>
|
||||
<string name="remote_control">التّحكّم البعيد</string>
|
||||
<string name="settings">إعدادات كدي المتّصل</string>
|
||||
<string name="mpris_play">شغّل</string>
|
||||
<string name="mpris_previous">السّابق</string>
|
||||
<string name="mpris_rew">رجوع</string>
|
||||
<string name="mpris_ff">تقدّم سريع</string>
|
||||
<string name="mpris_next">التّالي</string>
|
||||
<string name="mpris_volume">المستوى</string>
|
||||
<string name="mpris_settings">إعدادات الوسائط المتعدّدة</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 ثوان</item>
|
||||
<item>20 ثانية</item>
|
||||
<item>30 ثانية</item>
|
||||
<item>دقيقة واحدة</item>
|
||||
<item>دقيقتان</item>
|
||||
</string-array>
|
||||
<string name="share_to">شارك مع...</string>
|
||||
<string name="protocol_version_older">يستخدم هذا الجهاز إصدار ميفاق أقدم</string>
|
||||
<string name="protocol_version_newer">يستخدم هذا الجهاز إصدار ميفاق أحدث</string>
|
||||
<string name="general_settings">إعدادات عامّة</string>
|
||||
<string name="plugin_settings">إعدادات</string>
|
||||
<string name="plugin_settings_with_name">إعدادات %s</string>
|
||||
<string name="device_name">اسم الجهاز</string>
|
||||
<string name="device_name_preference_summary">%s</string>
|
||||
<string name="invalid_device_name">اسم جهاز غير صالح</string>
|
||||
<string name="shareplugin_text_saved">استُقبل نصّ، حُفظ إلى الحافظة</string>
|
||||
<string name="custom_devices_settings">قائمة أجهزة مخصّصة</string>
|
||||
<string name="custom_device_list">أضف أجهزة بميفاق الإنترنت مإ</string>
|
||||
<string name="share_notification_preference">إخطارات مزعجة</string>
|
||||
<string name="share_notification_preference_summary">اهتزّ وشغّل صوتًا عند استقبال ملفّ</string>
|
||||
<string name="sftp_internal_storage">التّخزين الدّاخليّ</string>
|
||||
<string name="sftp_all_files">كلّ الملفّات</string>
|
||||
<string name="sftp_sdcard_num">بطاقة SD %d</string>
|
||||
<string name="sftp_sdcard">بطاقة SD</string>
|
||||
<string name="sftp_readonly">(للقراءة فقط)</string>
|
||||
<string name="sftp_camera">صور الكاميرا</string>
|
||||
<string name="add_host">أضف مضيفًا/مإ</string>
|
||||
<string name="no_players_connected">لم يُعثر على مشغّلات</string>
|
||||
<string name="custom_dev_list_help">استخدم هذا الخيار فقط إن لم يُكتَشف جهازك آليًّا. أدخِل عنوان مإ أو اسم المضيف أدناه والمس الزرّ لإضافته إلى القائمة. المس عنصرًا موجودًا لإزالته من القائمة.</string>
|
||||
<string name="mpris_player_on_device">%1$s على %2$s</string>
|
||||
</resources>
|