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

Compare commits

...

55 Commits

Author SHA1 Message Date
Albert Vaca Cintora
e53338c70e Release 1.26.3 2023-07-02 23:34:46 +02:00
Albert Vaca Cintora
e641ff5a0a Don't call put if we are going to call remove right after
Note we still want the remove in case the plugin already was loaded
2023-07-02 23:26:56 +02:00
Albert Vaca Cintora
2ebaf6ae5b Ignore exceptions in pairingSuccessful callbacks
Bubbling up the exception to the PairingHandler makes the device become
unpaired, and we only want this if the error happens persisting device info
2023-07-02 23:01:05 +02:00
Albert Vaca Cintora
dd89463d75 Add logging to help debug a crash restoring saved devices 2023-07-02 22:57:40 +02:00
Albert Vaca Cintora
7194b308cb Fix NPE 2023-07-02 20:46:11 +02:00
Albert Vaca Cintora
328b708083 Fix NPE by checking if there's a Device 2023-07-02 10:27:52 +02:00
l10n daemon script
6404b86373 GIT_SILENT Sync po/docbooks with svn 2023-07-01 01:53:29 +00:00
l10n daemon script
3263b37c8a GIT_SILENT Sync po/docbooks with svn 2023-06-30 01:53:09 +00:00
Albert Vaca Cintora
a31476951a Release 1.26.2 2023-06-29 10:52:37 +02:00
Albert Vaca Cintora
1d105bbb3d Re-add nowPlaying for backwards compat with GSConnect 50 2023-06-29 10:48:02 +02:00
l10n daemon script
3154eef6a2 GIT_SILENT Sync po/docbooks with svn 2023-06-29 01:51:01 +00:00
l10n daemon script
97a0389d04 GIT_SILENT made messages (after extraction) 2023-06-29 00:46:34 +00:00
Albert Vaca Cintora
8c1603f6e4 Add DeviceInfo class
`DeviceInfo` contains all the properties we need to instantiate a `Device`:
id, name, type, cert, capabilities and protocol version. Before, we had a mix
of passing those around as arguments or passing identity packets (ie: json).
This simplifies the `Device` class quite a bit.

Now, `BaseLink` subclasses need to implement the `getDeviceInfo()` interface
that returns a `DeviceInfo`, which is what we will pass around and eventually
use to instantiate a `Device`.

This means that identity packets are an implementation detail of the
`LanLinkProvider` and that other implementations could get the `DeviceInfo`
in a different way.

In a future, we can add a mechanism for links to notify when their `DeviceInfo` changed.
This will allow us to better support device renames (which now are implemented by 
reconnecting to the device and don't always work) or sending the capabilities fields
later in the case of MacOS/iOS where we can't send them in a UDP packet due to the
size limit of 1500 bytes.
2023-06-27 11:14:36 +00:00
l10n daemon script
310e61b570 GIT_SILENT Sync po/docbooks with svn 2023-06-27 01:58:07 +00:00
l10n daemon script
bfa4d05e0d GIT_SILENT made messages (after extraction) 2023-06-27 00:47:27 +00:00
Albert Vaca Cintora
6568bb486c Synchronize access to fetchUrlList
Otherwise we could get ArrayIndexOutOfBoundsException
2023-06-27 02:34:00 +02:00
Albert Vaca Cintora
a46fa23419 Copy the ref to MpriMprisPlayer since the field can become null
Create a local copy of the reference, otherwise other fields can set the
field to null while the function is running and cause a NPE.
2023-06-27 02:26:10 +02:00
Krut Patel
a29aeaad92 mpris-receiver: Deregister callbacks when clearing players
## Summary
### Main issue
In essence, clearing `players` field inside `onActiveSessionsChanged` is not enough - we need to also deregister the `MprisReceiverCallback` from `MediaController` to ensure the `MprisReceiverPlayer` is truly destroyed.

### Details
* Spotify has a bad habit of causing lots of calls to `onActiveSessionsChanged` on song change. In each call, we clear the existing players and create new instances of `MprisReceiverPlayer` and `MprisReceiverCallback` to track media changes.
* Each `MprisReceiverCallback` instance gets registered with `MediaController` inside `createPlayer`.
* Inside `onActiveSessionsChanged`, if the underlying `MediaController` object remained alive, we would now end up with _two_ instances of `MprisReceiverCallback` that internally reference the same `MprisReceiverPlayer` object.

### Fix
Deregister the callbacks inside `onActiveSessionsChanged` before clearing `players` hashmap.

## Test Plan
* Add log line `Log.d(TAG, "sending Metadata");` just above `device.sendPacket(np);` inside `sendMetadata` of `MprisReceiverPlugin`.
* Start playing a song on spotify, and after a few secs, hit Next.\
* Monitor our logcat.

### Before:
<details><summary>Relevant logcat after Next was pressed</summary>

```txt
2023-06-25 18:56:57.395 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.403 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.413 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.422 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.436 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.454 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.484 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.499 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.524 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.544 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.554 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.562 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.569 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.579 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.592 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.603 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.613 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.629 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.641 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.653 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.666 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.671 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.679 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.686 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.694 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.699 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.704 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.713 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.719 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.724 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.730 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.743 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.750 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.759 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.770 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.785 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.793 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.799 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.808 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.812 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.819 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.831 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.838 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.844 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.849 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.855 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.861 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.866 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.872 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.877 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.884 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.892 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.898 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.903 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.907 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.913 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.917 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.921 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.928 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.934 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.938 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.944 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.949 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.954 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.959 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.966 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.971 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.976 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.983 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.990 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:57.995 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.000 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.007 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.013 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.021 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.026 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.032 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.041 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.047 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.055 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.062 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.067 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.072 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.078 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.083 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.088 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.096 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.106 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.114 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.120 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.127 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.135 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.143 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.149 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.157 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.164 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.174 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.180 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.186 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.195 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.201 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.209 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.216 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.223 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.231 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.238 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.243 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.251 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.262 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.269 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.275 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.281 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.287 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.294 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.302 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.310 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.319 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.327 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.337 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.345 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.350 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.357 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.363 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.369 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.374 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.382 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.389 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.398 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.404 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.411 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.419 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.427 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.434 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.441 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.449 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.453 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.458 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.464 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.471 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.475 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.483 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.490 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.497 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.502 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.510 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.519 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.532 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.536 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.543 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.552 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.560 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.567 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.574 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.580 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.587 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.592 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.599 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.606 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.613 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.620 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.626 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.633 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.642 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.652 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.659 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.667 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.675 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.683 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.690 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.699 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.706 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.712 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.718 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.726 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.734 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.742 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.748 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.753 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.759 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.765 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.774 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.781 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.787 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.794 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.801 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.806 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.811 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.817 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.823 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.831 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.839 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.847 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.854 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.862 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.870 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.875 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.879 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.886 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.892 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.898 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.904 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.911 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.919 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.927 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.934 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.939 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.945 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.953 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.959 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.969 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.979 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.986 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:58.993 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.001 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.007 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.014 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.021 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.027 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.034 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.040 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.046 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.053 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.060 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.067 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.075 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.083 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.092 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.099 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.107 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.114 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.121 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.129 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.136 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.142 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:56:59.226 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.228 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.229 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.231 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.233 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.234 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.234 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.235 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.236 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.236 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.237 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.237 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.238 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.238 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.239 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:56:59.239 31955-31955 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
```

</details>

### After:
<details><summary>Relevant logcat after Next was pressed</summary>

```txt
2023-06-25 18:54:57.412 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.534 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.547 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.558 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.567 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.579 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.590 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.595 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.603 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendMetadata
2023-06-25 18:54:57.655 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.656 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.667 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.678 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.680 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.683 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.701 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.711 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.714 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.716 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.726 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.729 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.739 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.742 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.743 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
2023-06-25 18:54:57.746 30742-30742 MprisReceiver           org.kde.kdeconnect_tp                D  sendPlayerList
```
</details>


## Future work
There are still some redundant calls to `sendMetadata`. These are coming from multiple calls to `onPlaybackStateChanged` from Spotify. To reduce these, we'd need to add a debouncer.

Another thing- we can see that `sendPlayerList` packet is also being sent quite a lot.
1. We probably need to be smarter about creating new instances of `MprisReceiverPlayer` inside `onActiveSessionsChanged` instead of the nuke-all-and-rebuild approach we have today.
2. Maybe we can implement a debouncer inside `onActiveSessionsChanged` to avoid sending too many packets.
2023-06-25 15:20:11 +00:00
l10n daemon script
79744dc17b GIT_SILENT made messages (after extraction) 2023-06-25 00:51:53 +00:00
Albert Vaca Cintora
096cf3f5f5 Release 1.26.1 2023-06-24 15:57:21 +02:00
l10n daemon script
10352e53c2 GIT_SILENT made messages (after extraction) 2023-06-24 00:49:55 +00:00
Albert Vaca Cintora
cbeaa72845 Added a fastlane README 2023-06-21 09:58:18 +02:00
Albert Vaca Cintora
41e296b16d Better exception handling in LanLinkProvider
Bubble up exceptions instead of using giant, generic try-catch blocks.

The UDP and TCP listener loops are now where we catch all the exceptions that might happen handling the incoming packets.

Also, the creation of worker threads now happens in the listener loops as well instead of the inner functions.

Finally the `broadcastUdpPacket` function has been split in `broadcastUdpIdentityPacket` and `sendUdpIdentityPacket` (the first calls the second).
2023-06-20 08:26:00 +00:00
l10n daemon script
8eb35028a1 GIT_SILENT Sync po/docbooks with svn 2023-06-20 02:25:35 +00:00
l10n daemon script
f9486204a5 GIT_SILENT made messages (after extraction) 2023-06-20 00:50:47 +00:00
Albert Vaca Cintora
46c32365ba Remove unused class 2023-06-19 18:36:57 +02:00
Albert Vaca Cintora
9dfa5bc51c Update the pairstate before calling any callbacks 2023-06-19 18:03:29 +02:00
Albert Vaca Cintora
fbf77fa103 Log when refreshing the device list 2023-06-19 18:03:29 +02:00
l10n daemon script
06a486d99b GIT_SILENT Sync po/docbooks with svn 2023-06-19 02:04:10 +00:00
l10n daemon script
2072128a6f GIT_SILENT Sync po/docbooks with svn 2023-06-18 02:39:49 +00:00
l10n daemon script
a7dc9e4249 GIT_SILENT Add new file (after extraction) 2023-06-18 00:52:50 +00:00
l10n daemon script
5a27a613ea GIT_SILENT made messages (after extraction) 2023-06-18 00:52:41 +00:00
Albert Vaca Cintora
ad48a25d79 Fix networkpacket getter visibility 2023-06-16 22:53:44 +02:00
Albert Vaca Cintora
2a273ff07f Remove redundant comments 2023-06-16 22:00:48 +02:00
Albert Vaca Cintora
bd0b03eafb Make this more readable 2023-06-16 21:49:39 +02:00
Albert Vaca Cintora
40b791a7c4 Only try to send unpair packet if reachable 2023-06-16 19:57:41 +02:00
Albert Vaca Cintora
867bdfb6fb Move PairingHandler out from the UI package 2023-06-16 19:51:57 +02:00
Albert Vaca Cintora
51312f9a25 Lower log level even more 2023-06-16 19:45:15 +02:00
Albert Vaca Cintora
e4743002be Fix infinite loop when already paired devices ask to pair 2023-06-16 19:44:11 +02:00
Albert Vaca Cintora
12de65f234 Rename "signal" function to match the name of the listeners' interface 2023-06-16 19:23:05 +02:00
Albert Vaca Cintora
6d089093e9 Lower log level 2023-06-16 19:23:04 +02:00
Albert Vaca Cintora
ffd99858e6 Remove interface not needed 2023-06-16 19:23:04 +02:00
Albert Vaca Cintora
6879e40341 Null check not needed 2023-06-16 19:23:04 +02:00
l10n daemon script
cf28c9c7dc GIT_SILENT Sync po/docbooks with svn 2023-06-16 01:48:53 +00:00
Albert Vaca Cintora
9d1cd05ce4 Fix LinkProviders' onNetworkChanged called before onStart 2023-06-15 14:52:00 +02:00
Albert Vaca Cintora
3e595cb262 Rename computer -> device 2023-06-15 14:45:54 +02:00
l10n daemon script
636c70ff06 GIT_SILENT Sync po/docbooks with svn 2023-06-15 01:55:19 +00:00
l10n daemon script
d68ccd69e0 GIT_SILENT Add new file (after extraction) 2023-06-15 00:46:19 +00:00
l10n daemon script
a9e8050aeb GIT_SILENT made messages (after extraction) 2023-06-15 00:46:10 +00:00
Albert Vaca Cintora
81270f724d Remove reverseConnectionBlackList used to support Android < 4 2023-06-14 18:17:21 +02:00
Albert Vaca Cintora
1ef3d75eb1 Update comments 2023-06-14 18:16:26 +02:00
l10n daemon script
37c8a41778 GIT_SILENT Sync po/docbooks with svn 2023-06-14 01:50:22 +00:00
l10n daemon script
ad9d375299 GIT_SILENT Sync po/docbooks with svn 2023-06-13 01:55:11 +00:00
l10n daemon script
d6647e44b9 GIT_SILENT Add new file (after extraction) 2023-06-13 00:47:22 +00:00
l10n daemon script
1d15cdba27 GIT_SILENT made messages (after extraction) 2023-06-13 00:47:14 +00:00
67 changed files with 1194 additions and 719 deletions

View File

@@ -9,8 +9,8 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.kde.kdeconnect_tp"
android:versionCode="12600"
android:versionName="1.26.0">
android:versionCode="12603"
android:versionName="1.26.3">
<uses-feature
android:name="android.hardware.telephony"

7
fastlane/README Normal file
View File

@@ -0,0 +1,7 @@
To upload translations to the Play Store, run from the root of the repo:
```
fastlane supply --skip_upload_screenshots --skip_upload_images --skip_upload_changelogs --json-key <path to the json key file>
```
F-Droid reads them directly from this directory for each release tag, so no action is needed.

View File

@@ -0,0 +1,14 @@
KDE connect cihazlarınız arasında inteqrasiya üçün funksiyalar dəstini təqdim edir:
- Mübadilə yaddaşının paylaşılması: cihazlarınız arasında kopyalayın və yerləşdirin.
- İstənilən tətbiqdən komputeriniz ilə URL ünvanlarını və faylları paylaşın.
- Kompyyuterinizdə gələn zənglər və SMS ismarıcları haqqında bildirişlər alın.
- Virtual toxunma paneli: Telefonunuzun ektranını kompyuterin toxunma paneli kimi istifdə edin.
- Bildirişlərin eyniləşdirilməsi: Android bildirişlərinizi kompyuterinizin iş masasından ozuyun.
- Multimedianın məsafədən idarə edilməsi: Linux media oxuducusunu telefonunuzdan idarə edin.
- WiFi bağlantısı: USB qoşulması və ya Bluetooth qoşulmasına ehtiyyac yoxdur.
- Ucdan-uca TLC şifrələmə: məlumatlarınızın təhlükəsizliyi qorunur.
Nəzərə alın ki, bu tətbiqin işləməsi üçün kompyuterinizə KDE Connect-i quraşdırmalısınız və sonuncu funksiyaların işləməsi üçün İş masası və Android versiyalarını sonuncu versiyaya eyni şəkildə yeniləməlisiniz.
Bu tətbiq açıq qaynaq layihəsinin bir hissəsidir və ona töhvə verənlərin sayəsində mövcuddur. Mənbə kodunu əldə etmək üçün veb-səhifəyə daxil olun.

View File

@@ -0,0 +1 @@
KDE Connect kompyuteriniz ilə smartfonunuzu inteqrasiya edir

View File

@@ -0,0 +1 @@
KDE Connect

View File

@@ -0,0 +1,9 @@
1.26.1:
* Fix infinite loop that would cause high CPU usage.
1.26.0:
* Allow having different widgets for diferent devices.
* Add stats about network packets sent and received.
* Add the option to cancel a pairing request after sending it.
* Fix device name set initially not being human-friendly.
* Rewrite some of the internals to improve performance.

View File

@@ -0,0 +1,12 @@
1.26.2:
* Fixed several bugs and crashes related to media controls.
1.26.1:
* Fix infinite loop that would cause high CPU usage.
1.26.0:
* Allow having different widgets for diferent devices.
* Add stats about network packets sent and received.
* Add the option to cancel a pairing request after sending it.
* Fix device name set initially not being human-friendly.
* Rewrite some of the internals to improve performance.

View File

@@ -0,0 +1,12 @@
1.26.3:
* Fixed several bugs and crashes related to media controls.
1.26.1:
* Fix infinite loop that would cause high CPU usage.
1.26.0:
* Allow having different widgets for diferent devices.
* Add stats about network packets sent and received.
* Add the option to cancel a pairing request after sending it.
* Fix device name set initially not being human-friendly.
* Rewrite some of the internals to improve performance.

View File

@@ -0,0 +1,14 @@
KDE Connect provides a set of features to integrate your workflow across devices:
- Shared clipboard: copy and paste between your devices.
- Share files and URLs to your computer from any app.
- Get notifications for incoming calls and SMS messages on your PC.
- Virtual touchpad: Use your phone screen as your computer's touchpad.
- Notifications sync: Read your Android notifications from the desktop.
- Multimedia remote control: Use your phone as a remote for Linux media players.
- WiFi connection: no USB wire or bluetooth needed.
- End-to-end TLS encryption: your information is safe.
Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.
This app is part of an open source project and it exists thanks to all the people who contributed to it. Visit the website to grab the source code.

View File

@@ -0,0 +1 @@
KDE Connect integrates your smartphone and computer

View File

@@ -0,0 +1 @@
KDE Connect

View File

@@ -1 +1 @@
KDE Connect integrates your smartphone and computer
KDE Connect intègre votre téléphone et votre ordinateur.

View File

@@ -0,0 +1,14 @@
KDE Connect ponuja niz funkcij za integracijo delovnega procesa na različnih napravah:
- skupno odložišče: kopirajte in lepite med napravami;
- datoteke in URL-je lahko z računalnikom delite iz poljubnega programa;
- prejemanje obvestil o dohodnih klicih in sporočilih SMS na računalniku;
- virtualna sledilna plošča: uporabite zaslon telefona kot sledilno tablico na računalniku;
- sinhronizacija obvestil: preberite obvestila iz sistema Android na namizju;
- večpredstavnostni daljinski upravljalnik: uporabite telefon kot daljinski upravljalnik za večpredstavnostne predvajalnike na Linuxu;
- povezava WiFi: ne potrebujete žice USB ali bluetootha;
- šifriranje TLS od enega konca do drugega: vaši podatki so varni.
Upoštevajte, da morate za delovanje tega programa na računalnik namestiti program KDE Connect in posodobiti namizno različico z različico za Android, da bodo delovale najnovejše funkcije.
Ta program je del odprto-kodnega projekta in obstaja po zaslugi vseh ljudi, ki so prispevali. Obiščite spletno mesto in si zagotovite izvorno kodo.

View File

@@ -0,0 +1 @@
KDE Connect integrira vaš pametni telefon in računalnik

View File

@@ -0,0 +1 @@
KDE Connect

View File

@@ -0,0 +1,14 @@
KDE Connect 提供了一系列用于整合不同设备的功能特性:
- 剪贴板共享:跨设备复制粘贴内容。
- 共享任意应用的文件和 URL 到电脑。
- 在电脑上获取关于来电和短信的通知。
- 虚拟触摸板:将手机屏幕当作电脑的触摸板使用。
- 提醒同步:在电脑桌面端读取安卓端的通知。
- 多媒体远程控制:用智能手机遥控 Linux 媒体播放器。
- WiFi 连接:无需 USB 线或者蓝牙。
- 端到端的 TLS 加密:确保您的信息安全。
请注意:您需要在您的电脑上安装 KDE Connect 才能使这款应用正常工作。请保持桌面端和安卓端的 KDE Connect 同步更新到一致的版本以便使用它们的最新功能。
此应用是一个自由开源软件项目的一部分。它的存续有赖于所有为它做出过贡献的人士。请访问项目主页以获取它的源代码。

View File

@@ -0,0 +1 @@
KDE Connect 可以整合您的智能手机和电脑

View File

@@ -0,0 +1 @@
KDE Connect

View File

@@ -0,0 +1,63 @@
# Kheyyam <xxmn77@gmail.com>, 2023.
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-12 16:45+0400\n"
"Last-Translator: Kheyyam <xxmn77@gmail.com>\n"
"Language-Team: Azerbaijani <kde-i18n-doc@kde.org>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 23.04.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid ""
"KDE Connect provides a set of features to integrate your workflow across "
"devices:\n"
"\n"
"- Shared clipboard: copy and paste between your devices.\n"
"- Share files and URLs to your computer from any app.\n"
"- Get notifications for incoming calls and SMS messages on your PC.\n"
"- Virtual touchpad: Use your phone screen as your computer's touchpad.\n"
"- Notifications sync: Read your Android notifications from the desktop.\n"
"- Multimedia remote control: Use your phone as a remote for Linux media "
"players.\n"
"- WiFi connection: no USB wire or bluetooth needed.\n"
"- End-to-end TLS encryption: your information is safe.\n"
"\n"
"Please note you will need to install KDE Connect on your computer for this "
"app to work, and keep the desktop version up-to-date with the Android "
"version for the latest features to work.\n"
"\n"
"This app is part of an open source project and it exists thanks to all the "
"people who contributed to it. Visit the website to grab the source code."
msgstr ""
"KDE connect cihazlarınız arasında inteqrasiya üçün funksiyalar dəstini "
"təqdim edir:\n"
"\n"
"- Mübadilə yaddaşının paylaşılması: cihazlarınız arasında kopyalayın və "
"yerləşdirin.\n"
"- İstənilən tətbiqdən komputeriniz ilə URL ünvanlarını və faylları "
"paylaşın.\n"
"- Kompyyuterinizdə gələn zənglər və SMS ismarıcları haqqında bildirişlər "
"alın.\n"
"- Virtual toxunma paneli: Telefonunuzun ektranını kompyuterin toxunma paneli "
"kimi istifdə edin.\n"
"- Bildirişlərin eyniləşdirilməsi: Android bildirişlərinizi kompyuterinizin "
"iş masasından ozuyun.\n"
"- Multimedianın məsafədən idarə edilməsi: Linux media oxuducusunu "
"telefonunuzdan idarə edin.\n"
"- WiFi bağlantısı: USB qoşulması və ya Bluetooth qoşulmasına ehtiyyac "
"yoxdur.\n"
"- Ucdan-uca TLC şifrələmə: məlumatlarınızın təhlükəsizliyi qorunur.\n"
"\n"
"Nəzərə alın ki, bu tətbiqin işləməsi üçün kompyuterinizə KDE Connect-i "
"quraşdırmalısınız və sonuncu funksiyaların işləməsi üçün İş masası və "
"Android versiyalarını sonuncu versiyaya eyni şəkildə yeniləməlisiniz.\n"
"\n"
"Bu tətbiq açıq qaynaq layihəsinin bir hissəsidir və ona töhvə verənlərin "
"sayəsində mövcuddur. Mənbə kodunu əldə etmək üçün veb-səhifəyə daxil olun."

View File

@@ -0,0 +1,19 @@
# Kheyyam <xxmn77@gmail.com>, 2023.
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-12 16:46+0400\n"
"Last-Translator: Kheyyam <xxmn77@gmail.com>\n"
"Language-Team: Azerbaijani <kde-i18n-doc@kde.org>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 23.04.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "KDE Connect kompyuteriniz ilə smartfonunuzu inteqrasiya edir"

View File

@@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2019-06-30 11:38+0200\n"
"PO-Revision-Date: 2023-06-08 22:08+0200\n"
"PO-Revision-Date: 2023-06-22 20:53+0200\n"
"Last-Translator: Mincho Kondarev <mkondarev@yahoo.de>\n"
"Language: bg\n"
"MIME-Version: 1.0\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Bulgarian <kde-i18n-doc@kde.org>\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 23.04.1\n"
"X-Generator: Lokalize 23.04.2\n"
#: dummy:1
msgid "Integrate Android with the KDE Plasma Desktop."
@@ -51,31 +51,36 @@ msgid ""
"This app is part of an open source project, visit the website to grab the"
" sources.\n"
msgstr ""
"KDE Connect предоставя няколко функции за интегриране на телефона и компютъра"
" ви:\n"
"KDE Connect предоставя няколко функции за интегриране на телефона с "
"компютъра:\n"
"\n"
"- Споделяне на файлове и URL адреси в KDE от всяко приложение, без кабели.\n"
"- Безжично поделяне на файлове и URL адреси в KDE от всяко приложение.\n"
"- Емулация на тъчпад: Използвайте екрана на телефона си като тъчпад на"
" компютъра*.\n"
"- Синхронизиране на известия (4.3+): Четете известията си за Android от"
" десктопа.\n"
"- Споделен клипборд: копирайте и поставяйте между телефона и компютъра.\n"
"- Синхронизиране на известия (4.3+): Четете известията си за Android от "
"десктопа.\n"
"- Споделен клипборд: копиране и поставяне между телефона и компютъра.\n"
"- Мултимедийно дистанционно управление: Използвайте телефона си като"
" дистанционно управление за Linux мултимедийни плейъри.\n"
" дистанционно управление за Linux мултимедийни "
"плейъри.\n"
"- WiFi връзка: не е необходим USB кабел или Bluetooth.\n"
"- RSA криптиране: информацията ви е в безопасност.\n"
"- RSA криптиране: информацията ви е сигурна.\n"
"\n"
"Моля, обърнете внимание, че за да работи това приложение, трябва да"
" инсталирате KDE Connect на компютъра си и да поддържате версията за настолни"
" компютри актуализирана с версията за Android, за да работят най-новите"
" функции.\n"
"Моля, обърнете внимание, че за тази функция ще трябва да инсталирате KDE"
" Connect на компютъра си "
"и да поддържате версията за настолни компютри в актуално състояние с версията"
" за Android, "
"за да работят най-новите функции.\n"
"\n"
"*Забележка за потребителите на Ubuntu: Ubuntu не актуализира своите хранилища"
" толкова бързо, колкото се актуализира това приложение. Някои функции няма да"
" работят, ако версията на KDE Connect в десктопа ви не съвпада с тази в"
" телефона ви. За да сте сигурни, че винаги имате най-новата версия на"
" десктопа си, използвайте това PPA хранилище:"
" https://code.launchpad.net/~vikoadi/+archive/ubuntu/ppa/ \n"
"*Забележка за потребителите на Ubuntu: Ubuntu не актуализират своите"
" хранилища "
"толкова бързо, колкото се обновява това приложение. Някои функции няма да"
" работят, ако KDE "
"Connect в десктопа не съвпада с версията в телефона ви. За да да сте сигурни,"
" че винаги "
"имате най-новата версия на десктопа си, използвайте това PPA "
"хранилище: https://code.launchpad.net/~vikoadi/+archive/ubuntu/ppa/ \n"
"\n"
"Това приложение е част от проект с отворен код, посетете уебсайта, за да"
" изтеглите изходния код.\n"
" изтеглите "
"изходния код.\n"

View File

@@ -0,0 +1,57 @@
# Steve Allewell <steve.allewell@gmail.com>, 2023.
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-17 12:11+0100\n"
"Last-Translator: Steve Allewell <steve.allewell@gmail.com>\n"
"Language-Team: British English\n"
"Language: en_GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 23.03.70\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid ""
"KDE Connect provides a set of features to integrate your workflow across "
"devices:\n"
"\n"
"- Shared clipboard: copy and paste between your devices.\n"
"- Share files and URLs to your computer from any app.\n"
"- Get notifications for incoming calls and SMS messages on your PC.\n"
"- Virtual touchpad: Use your phone screen as your computer's touchpad.\n"
"- Notifications sync: Read your Android notifications from the desktop.\n"
"- Multimedia remote control: Use your phone as a remote for Linux media "
"players.\n"
"- WiFi connection: no USB wire or bluetooth needed.\n"
"- End-to-end TLS encryption: your information is safe.\n"
"\n"
"Please note you will need to install KDE Connect on your computer for this "
"app to work, and keep the desktop version up-to-date with the Android "
"version for the latest features to work.\n"
"\n"
"This app is part of an open source project and it exists thanks to all the "
"people who contributed to it. Visit the website to grab the source code."
msgstr ""
"KDE Connect provides a set of features to integrate your workflow across "
"devices:\n"
"\n"
"- Shared clipboard: copy and paste between your devices.\n"
"- Share files and URLs to your computer from any app.\n"
"- Get notifications for incoming calls and SMS messages on your PC.\n"
"- Virtual touchpad: Use your phone screen as your computer's touchpad.\n"
"- Notifications sync: Read your Android notifications from the desktop.\n"
"- Multimedia remote control: Use your phone as a remote for Linux media "
"players.\n"
"- WiFi connection: no USB wire or bluetooth needed.\n"
"- End-to-end TLS encryption: your information is safe.\n"
"\n"
"Please note you will need to install KDE Connect on your computer for this "
"app to work, and keep the desktop version up-to-date with the Android "
"version for the latest features to work.\n"
"\n"
"This app is part of an open source project and it exists thanks to all the "
"people who contributed to it. Visit the website to grab the source code."

View File

@@ -0,0 +1,19 @@
# Steve Allewell <steve.allewell@gmail.com>, 2023.
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-17 12:11+0100\n"
"Last-Translator: Steve Allewell <steve.allewell@gmail.com>\n"
"Language-Team: British English\n"
"Language: en_GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 23.03.70\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "KDE Connect integrates your smartphone and computer"

View File

@@ -13,7 +13,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 20.04.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "KDE Connect integra tu teléfono inteligente y tu equipo"

View File

@@ -1,17 +1,19 @@
# Xavier BESNARD <xavier.besnard@neuf.fr>, 2023.
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: kdeconnect-android-store-short\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-08 05:31+0200\n"
"Last-Translator: KDE Francophone <kde-francophone@kde.org>\n"
"Language-Team: KDE Francophone <kde-francophone@kde.org>\n"
"PO-Revision-Date: 2023-06-27 08:54+0200\n"
"Last-Translator: Xavier BESNARD <xavier.besnard@neuf.fr>\n"
"Language-Team: fr\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 23.04.2\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr ""
msgstr "KDE Connect intègre votre téléphone et votre ordinateur."

View File

@@ -0,0 +1,19 @@
# Luiz Fernando Ranghetti <elchevive@opensuse.org>, 2023.
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-26 17:19-0300\n"
"Last-Translator: Luiz Fernando Ranghetti <elchevive@opensuse.org>\n"
"Language-Team: Brazilian Portuguese <kde-i18n-pt_BR@kde.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 22.12.3\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "O KDE Connect integra seu celular e computador"

View File

@@ -0,0 +1,65 @@
# Translations template for KDEConnect.
# Copyright (C) 2023 KDE
# This file is distributed under the same license as the KDE project.
#
# Martin Srebotnjak <miles@filmsi.net>, 2023.
#
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-14 17:25+0200\n"
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
"Language-Team: Slovenian <kde-i18n-doc@kde.org>\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
"%100==4 ? 3 : 0);\n"
msgid ""
"KDE Connect provides a set of features to integrate your workflow across "
"devices:\n"
"\n"
"- Shared clipboard: copy and paste between your devices.\n"
"- Share files and URLs to your computer from any app.\n"
"- Get notifications for incoming calls and SMS messages on your PC.\n"
"- Virtual touchpad: Use your phone screen as your computer's touchpad.\n"
"- Notifications sync: Read your Android notifications from the desktop.\n"
"- Multimedia remote control: Use your phone as a remote for Linux media "
"players.\n"
"- WiFi connection: no USB wire or bluetooth needed.\n"
"- End-to-end TLS encryption: your information is safe.\n"
"\n"
"Please note you will need to install KDE Connect on your computer for this "
"app to work, and keep the desktop version up-to-date with the Android "
"version for the latest features to work.\n"
"\n"
"This app is part of an open source project and it exists thanks to all the "
"people who contributed to it. Visit the website to grab the source code."
msgstr ""
"KDE Connect ponuja niz funkcij za integracijo delovnega procesa na različnih "
"napravah:\n"
"\n"
"- skupno odložišče: kopirajte in lepite med napravami;\n"
"- datoteke in URL-je lahko z računalnikom delite iz poljubnega programa;\n"
"- prejemanje obvestil o dohodnih klicih in sporočilih SMS na računalniku;\n"
"- virtualna sledilna plošča: uporabite zaslon telefona kot sledilno tablico "
"na računalniku;\n"
"- sinhronizacija obvestil: preberite obvestila iz sistema Android na "
"namizju;\n"
"- večpredstavnostni daljinski upravljalnik: uporabite telefon kot daljinski "
"upravljalnik za večpredstavnostne predvajalnike na Linuxu;\n"
"- povezava WiFi: ne potrebujete žice USB ali bluetootha;\n"
"- šifriranje TLS od enega konca do drugega: vaši podatki so varni.\n"
"\n"
"Upoštevajte, da morate za delovanje tega programa na računalnik namestiti "
"program KDE Connect in posodobiti namizno različico z različico za Android, "
"da bodo delovale najnovejše funkcije.\n"
"\n"
"Ta program je del odprto-kodnega projekta in obstaja po zaslugi vseh ljudi, "
"ki so prispevali. Obiščite spletno mesto in si zagotovite izvorno kodo."

View File

@@ -0,0 +1,25 @@
# Translations template for KDEConnect.
# Copyright (C) 2023 KDE
# This file is distributed under the same license as the KDE project.
#
# Martin Srebotnjak <miles@filmsi.net>, 2023.
#
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-14 17:26+0200\n"
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
"Language-Team: Slovenian <kde-i18n-doc@kde.org>\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
"%100==4 ? 3 : 0);\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "KDE Connect integrira vaš pametni telefon in računalnik"

View File

@@ -0,0 +1,59 @@
#. extracted from ./metadata/android/en-US/full_description.txt
msgid ""
msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-17 04:11\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 2.5.0\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: kdeorg\n"
"X-Crowdin-Project-ID: 269464\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /kf5-trunk/messages/kdeconnect-android/kdeconnect-android-"
"store-full.pot\n"
"X-Crowdin-File-ID: 43897\n"
msgid ""
"KDE Connect provides a set of features to integrate your workflow across "
"devices:\n"
"\n"
"- Shared clipboard: copy and paste between your devices.\n"
"- Share files and URLs to your computer from any app.\n"
"- Get notifications for incoming calls and SMS messages on your PC.\n"
"- Virtual touchpad: Use your phone screen as your computer's touchpad.\n"
"- Notifications sync: Read your Android notifications from the desktop.\n"
"- Multimedia remote control: Use your phone as a remote for Linux media "
"players.\n"
"- WiFi connection: no USB wire or bluetooth needed.\n"
"- End-to-end TLS encryption: your information is safe.\n"
"\n"
"Please note you will need to install KDE Connect on your computer for this "
"app to work, and keep the desktop version up-to-date with the Android "
"version for the latest features to work.\n"
"\n"
"This app is part of an open source project and it exists thanks to all the "
"people who contributed to it. Visit the website to grab the source code."
msgstr ""
"KDE Connect 提供了一系列用于整合不同设备的功能特性:\n"
"\n"
"- 剪贴板共享:跨设备复制粘贴内容。\n"
"- 共享任意应用的文件和 URL 到电脑。\n"
"- 在电脑上获取关于来电和短信的通知。\n"
"- 虚拟触摸板:将手机屏幕当作电脑的触摸板使用。\n"
"- 提醒同步:在电脑桌面端读取安卓端的通知。\n"
"- 多媒体远程控制:用智能手机遥控 Linux 媒体播放器。\n"
"- WiFi 连接:无需 USB 线或者蓝牙。\n"
"- 端到端的 TLS 加密:确保您的信息安全。\n"
"\n"
"请注意:您需要在您的电脑上安装 KDE Connect 才能使这款应用正常工作。请保持桌面"
"端和安卓端的 KDE Connect 同步更新到一致的版本以便使用它们的最新功能。\n"
"\n"
"此应用是一个自由开源软件项目的一部分。它的存续有赖于所有为它做出过贡献的人"
"士。请访问项目主页以获取它的源代码。"

View File

@@ -0,0 +1,24 @@
#. extracted from ./metadata/android/en-US/short_description.txt
msgid ""
msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-07 00:47+0000\n"
"PO-Revision-Date: 2023-06-17 04:11\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 2.5.0\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: kdeorg\n"
"X-Crowdin-Project-ID: 269464\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /kf5-trunk/messages/kdeconnect-android/kdeconnect-android-"
"store-short.pot\n"
"X-Crowdin-File-ID: 43899\n"
msgid "KDE Connect integrates your smartphone and computer"
msgstr "KDE Connect 可以整合您的智能手机和电脑"

View File

@@ -108,6 +108,7 @@
<string name="device_menu_plugins">Plaqin ayarları</string>
<string name="device_menu_unpair">Ayırmaq</string>
<string name="pair_new_device">Yeni cihaz qoşmaq</string>
<string name="cancel_pairing">Qoşulmanı ləğv edin</string>
<string name="unknown_device">Naməlum Cihaz</string>
<string name="error_not_reachable">Cihaz əlçatmazdır</string>
<string name="error_already_paired">Cihaz artıq qoşulub</string>
@@ -393,6 +394,7 @@
<string name="holger_kaelberer_task">Uzaq klaviatura əlavəsi və xəta sazlamaları</string>
<string name="saikrishna_arcot_task">Uzaq daxiletmə əlavəsində klaviaturanın istifadəsini dəstəklənməsi, xəta sazlamaları və əsas yaxşılaşdırmalar</string>
<string name="everyone_else">İllər boyu KDE Connect\'ə töhfə verən hər kəs</string>
<string name="send_clipboard">Mübadilə buferinə göndərin</string>
<string name="send_clipboard">Mübadilə yaddaşını göndərin</string>
<string name="tap_to_execute">İcra etmək üçün vurun</string>
<string name="plugin_stats">Plaqin statistikası</string>
</resources>

View File

@@ -101,7 +101,7 @@
<string name="view_status_title">Stav</string>
<string name="battery_status_format">Baterie: %d%%</string>
<string name="battery_status_low_format">Baterie: %d%% Téměř vybitá baterie</string>
<string name="battery_status_charging_format">Battery: %d%% nabíjí se</string>
<string name="battery_status_charging_format">Baterie: %d%% nabíjí se</string>
<string name="category_connected_devices">Připojená zařízení</string>
<string name="category_not_paired_devices">Dostupná zařízení</string>
<string name="category_remembered_devices">Zapamatovaná zařízení</string>
@@ -116,7 +116,7 @@
<string name="error_canceled_by_user">Přerušeno uživatelem</string>
<string name="error_canceled_by_other_peer">Přerušeno druhým uživatelem</string>
<string name="encryption_info_title">Informace o šifrování</string>
<string name="encryption_info_msg_no_ssl">Druhé zařízení nepoužívá poslední verzi KDE connect. Bude použita stará metoda šifrování.</string>
<string name="encryption_info_msg_no_ssl">Druhé zařízení nepoužívá poslední verzi KDE Connect. Bude použita stará metoda šifrování.</string>
<string name="my_device_fingerprint">Otisk SHA256 certifikátu vašeho zařízení je:</string>
<string name="remote_device_fingerprint">Otisk certifikátu SHA256 vzdáleného zařízení je:</string>
<string name="pair_requested">Bylo vyžádáno párování</string>
@@ -274,7 +274,7 @@
<string name="contacts_permission_explanation">Pro sdílení knihy kontaktů s pracovním prostředím, musíte udělit přístup ke kontaktům</string>
<string name="select_ringtone">Vybrat vyzváněcí tón</string>
<string name="telephony_pref_blocked_title">Blokovaná čísla</string>
<string name="telephony_pref_blocked_dialog_desc">Nezobrazovat volnání a SMS z těchto čísel. Prosím, zadejte pouze jedno slovo na řádek.</string>
<string name="telephony_pref_blocked_dialog_desc">Nezobrazovat volání a SMS z těchto čísel. Prosím, zadejte pouze jedno slovo na řádek.</string>
<string name="mpris_coverart_description">Obal současného média</string>
<string name="device_icon_description">Ikona zařízení</string>
<string name="settings_icon_description">Ikona nastavení</string>

View File

@@ -108,6 +108,7 @@
<string name="device_menu_plugins">Plugin settings</string>
<string name="device_menu_unpair">Unpair</string>
<string name="pair_new_device">Pair new device</string>
<string name="cancel_pairing">Cancel pairing</string>
<string name="unknown_device">Unknown device</string>
<string name="error_not_reachable">Device not reachable</string>
<string name="error_already_paired">Device already paired</string>
@@ -395,4 +396,5 @@
<string name="everyone_else">Everyone else who has contributed to KDE Connect over the years</string>
<string name="send_clipboard">Send clipboard</string>
<string name="tap_to_execute">Tap to execute</string>
<string name="plugin_stats">Plugin stats</string>
</resources>

View File

@@ -60,6 +60,8 @@
<string name="mousepad_mouse_buttons_title">Näytä hiiripainikkeet</string>
<string name="mousepad_acceleration_profile_settings_title">Aseta osoittimen kiihdytys</string>
<string name="mousepad_scroll_direction_title">Käänteinen vierityssuunta</string>
<string name="gyro_mouse_enabled_title">Käytä gyroskooppihiirtä</string>
<string name="gyro_mouse_sensitivity_title">Gyroskoopin herkkyys</string>
<string-array name="mousepad_tap_entries">
<item>Vasen napsautus</item>
<item>Oikea napsautus</item>
@@ -374,6 +376,7 @@
<string name="click_here_to_type">Napauta ja kirjoita</string>
<string name="clear_compose">Tyhjennä</string>
<string name="send_compose">Lähetä</string>
<string name="compose_send_title">Kirjoita teksti</string>
<string name="open_compose_send">Kirjoita teksti</string>
<string name="about_kde_about">&lt;h1&gt;Tietoa&lt;/h1&gt; &lt;p&gt;KDE on ohjelmoijien, taiteilijoiden, kirjoittajien, kääntäjien ja muiden sisällönluojien kansainvälinen yhteisö, joka on sitoutunut &lt;a href=https://www.gnu.org/philosophy/free-sw.html&gt;vapaiden ohjelmien&lt;/a&gt; kehitykseen. KDE tuottaa Plasma-työpöytäympäristöä, satoja sovelluksia ja monia niitä tukevia ohjelmakirjastoja.&lt;/p&gt; &lt;p&gt;KDE pyrkii yhteistyöhön: mikään yksittäinen toimija ei hallitse sen suuntaa tai tuotteita, vaan teemme yhdessä työtä yhteisen päämäärän hyväksi: tuottaaksemme maailman hienointa vapaata ohjelmistoa. Kaikki ovat tervetulleita &lt;a href=https://community.kde.org/Get_Involved&gt;liittymään ja avustamaan&lt;/a&gt; KDE:ta myös sinä.&lt;/p&gt; Sivulta &lt;a href=https://www.kde.org/&gt;https://www.kde.org/&lt;/a&gt; löytyy KDE-yhteisöstä ja tuottamistamme ohjelmista lisätietoa.</string>
<string name="about_kde_report_bugs_or_wishes">&lt;h1&gt;Ilmoita ohjelmavirheistä tai -toiveista&lt;/h1&gt; &lt;p&gt;Ohjelmia voi aina parantaa, ja KDE-yhteisö on siihen valmis. Sinun käyttäjän on kuitenkin kerrottava meille, kun jokin ei toimi odotetusti tai voisi toimia paremmin.&lt;/p&gt; &lt;p&gt;KDE:lla on virheenseurantajärjestelmä. Käy sivulla &lt;a href=https://bugs.kde.org/&gt;https://bugs.kde.org/&lt;/a&gt; tai käytä Tietoa-sivun painiketta ”Ilmoita ohjelmavirheestä”.&lt;/p&gt; Parannusehdotuksissakin olet tervetullut käyttämään virheenseurantajärjestelmää kirjataksesi toiveesi. Varmista, että käytät vakavuustasoa ”Wishlist”.</string>

View File

@@ -396,4 +396,5 @@
<string name="everyone_else">Toutes les autres personnes ayant contribué à « KDE Connect » depuis plusieurs années</string>
<string name="send_clipboard">Envoyer le presse-papier</string>
<string name="tap_to_execute">Tapotez pour lancer</string>
<string name="plugin_stats">Statistiques des modules externes</string>
</resources>

View File

@@ -33,7 +33,7 @@
<string name="pref_plugin_notifications">Sincronización de notificacións</string>
<string name="pref_plugin_notifications_desc">Acceda ás súas notificacións desde outros dispositivos</string>
<string name="pref_plugin_receive_notifications">Recibir notificacións</string>
<string name="pref_plugin_receive_notifications_desc">Recibir notificacións do outro dispositivo e mostralas en Android</string>
<string name="pref_plugin_receive_notifications_desc">Recibir notificacións do outro dispositivo e amosalas en Android</string>
<string name="pref_plugin_sharereceiver">Compartir e recibir</string>
<string name="pref_plugin_sharereceiver_desc">Comparta ficheiros e enderezos URL entre dispositivos</string>
<string name="device_list_empty">Non hai dispositivos.</string>
@@ -42,7 +42,7 @@
<string name="cancel">Cancelar</string>
<string name="open_settings">Abrir a configuración</string>
<string name="no_permissions">Debe conceder permisos para acceder ás notificacións</string>
<string name="no_permission_mprisreceiver">Para poder controlar os seus reprodutores de son e vídeo ten que garantir acceso ás notificacións</string>
<string name="no_permission_mprisreceiver">Para poder controlar os seus reprodutores multimedia ten que garantir acceso ás notificacións</string>
<string name="no_permissions_remotekeyboard">Para recibir presións de tecla ten que activar o teclado remoto de KDE Connect</string>
<string name="send_ping">Enviar un ping</string>
<string name="open_mpris_controls">Control multimedia</string>
@@ -57,7 +57,7 @@
<string name="mousepad_double_tap_settings_title">Definir a acción de tocar con dous dedos</string>
<string name="mousepad_triple_tap_settings_title">Definir a acción de tocar con tres dedos</string>
<string name="mousepad_sensitivity_settings_title">Definir a sensibilidade do punteiro táctil</string>
<string name="mousepad_mouse_buttons_title">Mostrar os botóns do rato</string>
<string name="mousepad_mouse_buttons_title">Amosar os botóns do rato</string>
<string name="mousepad_acceleration_profile_settings_title">Definir a aceleración do punteiro</string>
<string name="mousepad_scroll_direction_title">Inverter a dirección de desprazamento</string>
<string name="gyro_mouse_enabled_title">Activar o rato de xiroscopio</string>
@@ -159,7 +159,7 @@
<string name="tap_to_answer">Toque para contestar</string>
<string name="right_click">Enviar un clic secundario</string>
<string name="middle_click">Enviar un clic central</string>
<string name="show_keyboard">Mostrar o teclado</string>
<string name="show_keyboard">Amosar o teclado</string>
<string name="device_not_paired">O dispositivo non está emparellado</string>
<string name="request_pairing">Solicitar emparellarse</string>
<string name="pairing_accept">Aceptar</string>
@@ -183,7 +183,7 @@
<item>1 minuto</item>
<item>2 minutos</item>
</string-array>
<string name="mpris_notification_settings_title">Mostrar a notificación de control de reprodución.</string>
<string name="mpris_notification_settings_title">Amosar a notificación de control de reprodución.</string>
<string name="mpris_notification_settings_summary">Permitir controlar os reprodutores sen abrir KDE Connect</string>
<string name="share_to">Compartir con…</string>
<string name="protocol_version_newer">Este dispositivo usa unha versión máis nova do protocolo.</string>
@@ -215,9 +215,9 @@
<string name="sftp_storage_preference_storage_location">Lugar de almacenamento</string>
<string name="sftp_storage_preference_storage_location_already_configured">Este lugar xa está configurado</string>
<string name="sftp_storage_preference_click_to_select">premer para seleccionar</string>
<string name="sftp_storage_preference_display_name">Nome para mostrar</string>
<string name="sftp_storage_preference_display_name_already_used">Este nome para mostrar xa está a usarse</string>
<string name="sftp_storage_preference_display_name_cannot_be_empty">O nome para mostrar non pode estar baleiro</string>
<string name="sftp_storage_preference_display_name">Nome para amosar</string>
<string name="sftp_storage_preference_display_name_already_used">Este nome para amosar xa está a usarse</string>
<string name="sftp_storage_preference_display_name_cannot_be_empty">O nome para amosar non pode estar baleiro</string>
<string name="sftp_action_mode_menu_delete">Eliminar</string>
<string name="sftp_no_storage_locations_configured">Non se configuraron localizacións de almacenamento</string>
<string name="sftp_saf_permission_explanation">Para acceder a ficheiro remotamente ten que configurar lugares de almacenamento</string>
@@ -242,7 +242,7 @@
<string name="findmyphone_title">Atopar o móbil</string>
<string name="findmyphone_title_tablet">Atopar a tableta</string>
<string name="findmyphone_title_tv">Atopar o meu televisor</string>
<string name="findmyphone_description">Reproduce un son de chamada no dispositivo para que poida atopalo.</string>
<string name="findmyphone_description">Reproduce un ton de chamada no dispositivo para que poida atopalo.</string>
<string name="findmyphone_found">Atopeino</string>
<string name="open">Abrir</string>
<string name="close">Pechar</string>
@@ -256,9 +256,9 @@
<string name="telephony_permission_explanation">Para ver as chamadas de teléfono no escritorio ten que dar permiso aos rexistros de chamadas telefónicas e ao estado do teléfono</string>
<string name="telephony_optional_permission_explanation">Para ver o nome dun contacto en vez dun número de teléfono ten que dar acceso aos contactos do teléfono.</string>
<string name="contacts_permission_explanation">Para compartir o caderno de contactos co escritorio ten que dar permiso de contactos</string>
<string name="select_ringtone">Seleccione un son de chamada</string>
<string name="select_ringtone">Seleccione un ton de chamada</string>
<string name="telephony_pref_blocked_title">Números bloqueados</string>
<string name="telephony_pref_blocked_dialog_desc">Non mostrar chamadas nin SMS destes números. Indique un número por liña.</string>
<string name="telephony_pref_blocked_dialog_desc">Non amosar chamadas nin SMS destes números. Indique un número por liña.</string>
<string name="mpris_coverart_description">Portada da obra actual.</string>
<string name="device_icon_description">Icona do dispositivo.</string>
<string name="settings_icon_description">Icona da configuración.</string>
@@ -295,8 +295,8 @@
<string name="settings_rename">Nome do dispositivo</string>
<string name="settings_dark_mode">Tema escuro</string>
<string name="settings_more_settings_title">Máis opcións</string>
<string name="settings_more_settings_text">As opcións específicas dun dispositivo están en «Configuración dos complementos» no dispositivo.</string>
<string name="setting_persistent_notification">Mostrar unha notificación persistente</string>
<string name="settings_more_settings_text">As opcións específicas dun dispositivo atópanse en «Configuración dos complementos» no dispositivo.</string>
<string name="setting_persistent_notification">Amosar unha notificación persistente</string>
<string name="setting_persistent_notification_oreo">Notificación persistente</string>
<string name="setting_persistent_notification_description">Toque para activar ou desactivar na configuración de notificacións</string>
<string name="extra_options">Opcións adicionais</string>
@@ -381,7 +381,7 @@
<string name="open_compose_send">Escribir texto</string>
<string name="about_kde_about">"&lt;h1&gt;Sobre&lt;/h1&gt; &lt;p&gt;KDE é unha comunidade internacional de persoas dedicadas á enxeñaría de software, á arte, á documentación, á tradución e á creación, todas elas comprometidas co desenvolvemento de &lt;a href=https://www.gnu.org/philosophy/free-sw.html&gt;software libre&lt;/a&gt;. KDE produce o ambiente de escritorio Plasma, centos de aplicacións, e as moitas bibliotecas de software sobre as que estas están construídas.&lt;/p&gt; &lt;p&gt;KDE é un esforzo cooperativo: non hai unha única entidade que controle a súa dirección ou os seus produtos. No seu lugar, xuntámonos para traballar no obxectivo común de construír o mellor software libre do mundo. Todas as persoas son benvidas a &lt;a href=https://community.kde.org/Get_Involved&gt;unirse e colaborar&lt;/a&gt; en KDE, incluída vostede.&lt;/p&gt; Visite &lt;a href=https://www.kde.org/&gt;https://www.kde.org/&lt;/a&gt; para máis información sobre a comunidade KDE e o software que produce."</string>
<string name="about_kde_report_bugs_or_wishes">&lt;h1&gt;Informe de fallos ou pida melloras&lt;/h1&gt; &lt;p&gt;O software sempre pode mellorarse, e o equipo de KDE está preparado para facelo. Porén, vostede, a persoa usuaria, ten que avisarnos cando algo non funciona como espera ou podería mellorarse.&lt;/p&gt; &lt;p&gt;KDE ten un sistema de seguimento de fallos. Visite &lt;a href=https://bugs.kde.org/&gt;https://bugs.kde.org/&lt;/a&gt; ou use o botón de «Informar dun fallo» da pantalla de información para informar dun fallo.&lt;/p&gt; Se ten unha suxestión de mellora tamén pode usar o sistema de seguimento de fallos para rexistrala. Asegúrese nese caso de usar a severidade «Lista de desexos».</string>
<string name="about_kde_join_kde">&lt;h1&gt;Únase a KDE&lt;/h1&gt; &lt;p&gt;Non necesita coñecementos de enxeñaría de software para formar parte do equipo de KDE. Pode unirse aos equipos nacionais que traducen as interfaces dos programas. Pode crear imaxes, temas, sons, e mellorar a documentación. Vostede decide!&lt;/p&gt; &lt;p&gt;Visite &lt;a href=https://community.kde.org/Get_Involved&gt;https://community.kde.org/Get_Involved&lt;/a&gt; para informarse sobre os proxectos nos que pode participar.&lt;/p&gt; Se necesita máis información ou documentación, atopará o que necesita en &lt;a href=https://techbase.kde.org/&gt;https://techbase.kde.org/&lt;/a&gt;.</string>
<string name="about_kde_join_kde">&lt;h1&gt;Únase a KDE&lt;/h1&gt; &lt;p&gt;Non necesita saber desenvolver software para formar parte do equipo de KDE. Pode unirse aos equipos nacionais que traducen as interfaces dos programas. Pode crear imaxes, temas, sons, e mellorar a documentación. Vostede decide!&lt;/p&gt; &lt;p&gt;Visite &lt;a href=https://community.kde.org/Get_Involved&gt;https://community.kde.org/Get_Involved&lt;/a&gt; para informarse sobre os proxectos nos que pode participar.&lt;/p&gt; Se necesita máis información ou documentación, ten o que necesita en &lt;a href=https://techbase.kde.org/&gt;https://techbase.kde.org/&lt;/a&gt;.</string>
<string name="about_kde_support_kde">&lt;h1&gt;Apoie KDE&lt;/h1&gt; &lt;p&gt;O software de KDE está e estará sempre dispoñíbel de balde, porén crealo ten custos.&lt;/p&gt; &lt;p&gt;Para apoiar o seu desenvolvemento, a comunidade KDE formou o KDE e.V., unha organización sen ánimo de lucro fundada legalmente na Alemaña. KDE e.V. representa á comunidade KDE en asuntos legais e financeiros. Consulte &lt;a href=https://ev.kde.org/&gt;https://ev.kde.org/&lt;/a&gt; para máis información sobre KDE e.V.&lt;/p&gt; &lt;p&gt;KDE benefíciase de moitos tipos de contribucións, incluídas as monetarias. Usamos os fondos para cubrir gastos derivados de colaborar. A maiores, os fondos úsanse para asistencia legal e para organizar conferencias e encontros.&lt;/p&gt; &lt;p&gt;Animámoslle a apoiar os nosos esforzos cunha doazón monetaria, mediante un dos sistemas detallados en &lt;a href=https://www.kde.org/community/donations/&gt;https://www.kde.org/community/donations/&lt;/a&gt;.&lt;/p&gt; Moitas grazas de antemán polo seu apoio.</string>
<string name="maintainer_and_developer">Mantemento e desenvolvemento</string>
<string name="developer">Desenvolvemento</string>

View File

@@ -194,4 +194,5 @@
<string name="everyone_else">Alcun altere qui ha contribuite a KDE Connect durante le annos</string>
<string name="send_clipboard">Invia Area de transferentia</string>
<string name="tap_to_execute">Toccaper executar</string>
<string name="plugin_stats">"Statisticas de plugin "</string>
</resources>

View File

@@ -108,6 +108,7 @@
<string name="device_menu_plugins">Configuração dos plugins</string>
<string name="device_menu_unpair">Cancelar emparelhamento</string>
<string name="pair_new_device">Emparelhar novo dispositivo</string>
<string name="cancel_pairing">Cancelar emparelhamento</string>
<string name="unknown_device">Dispositivo desconhecido</string>
<string name="error_not_reachable">Dispositivo inacessível</string>
<string name="error_already_paired">Dispositivo já emparelhado</string>
@@ -395,4 +396,5 @@
<string name="everyone_else">Todos os outros que contribuíram para o KDE Connect ao longo dos anos</string>
<string name="send_clipboard">Enviar para área de transferência</string>
<string name="tap_to_execute">Toque para executar</string>
<string name="plugin_stats">Estatísticas do plugin</string>
</resources>

View File

@@ -108,6 +108,7 @@
<string name="device_menu_plugins">插件设置</string>
<string name="device_menu_unpair">取消配对</string>
<string name="pair_new_device">配对新设备</string>
<string name="cancel_pairing">取消配对</string>
<string name="unknown_device">未知设备</string>
<string name="error_not_reachable">设备不可及</string>
<string name="error_already_paired">设备已配对</string>
@@ -387,4 +388,5 @@
<string name="everyone_else">以及多年来为 KDE Connect 作出过贡献的其他所有人</string>
<string name="send_clipboard">发送剪贴板</string>
<string name="tap_to_execute">轻触执行</string>
<string name="plugin_stats">插件状态</string>
</resources>

View File

@@ -12,6 +12,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.NetworkPacket;
import java.io.IOException;
@@ -26,20 +27,20 @@ public abstract class BaseLink {
protected final Context context;
private final BaseLinkProvider linkProvider;
private final String deviceId;
private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
protected BaseLink(@NonNull Context context, @NonNull String deviceId, @NonNull BaseLinkProvider linkProvider) {
protected BaseLink(@NonNull Context context, @NonNull BaseLinkProvider linkProvider) {
this.context = context;
this.linkProvider = linkProvider;
this.deviceId = deviceId;
}
/* To be implemented by each link for pairing handlers */
public abstract String getName();
public abstract DeviceInfo getDeviceInfo();
public String getDeviceId() {
return deviceId;
return getDeviceInfo().id;
}
public BaseLinkProvider getLinkProvider() {
@@ -54,14 +55,14 @@ public abstract class BaseLink {
}
//Should be called from a background thread listening for packets
protected void packetReceived(@NonNull NetworkPacket np) {
public void packetReceived(@NonNull NetworkPacket np) {
for(PacketReceiver pr : receivers) {
pr.onPacketReceived(np);
}
}
public void disconnect() {
linkProvider.connectionLost(this);
linkProvider.onConnectionLost(this);
}
//TO OVERRIDE, should be sync. If sendPayloadFromSameThread is false, it should only block to send the packet but start a separate thread to send the payload.

View File

@@ -8,23 +8,17 @@ package org.kde.kdeconnect.Backends;
import androidx.annotation.NonNull;
import org.kde.kdeconnect.NetworkPacket;
import java.security.cert.Certificate;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class BaseLinkProvider {
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
public interface ConnectionReceiver {
void onConnectionReceived(@NonNull final String deviceId,
@NonNull final Certificate certificate,
@NonNull final NetworkPacket identityPacket,
@NonNull final BaseLink link);
void onConnectionReceived(@NonNull final BaseLink link);
void onConnectionLost(BaseLink link);
}
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
public void addConnectionReceiver(ConnectionReceiver cr) {
connectionReceivers.add(cr);
}
@@ -33,24 +27,26 @@ public abstract class BaseLinkProvider {
return connectionReceivers.remove(cr);
}
//These two should be called when the provider links to a new computer
protected void connectionAccepted(@NonNull final String deviceId,
@NonNull final Certificate certificate,
@NonNull final NetworkPacket identityPacket,
@NonNull final BaseLink link) {
//Log.i("KDE/LinkProvider", "connectionAccepted");
/**
* To be called from the child classes when a link to a new device is established
*/
protected void onConnectionReceived(@NonNull final BaseLink link) {
//Log.i("KDE/LinkProvider", "onConnectionReceived");
for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionReceived(deviceId, certificate, identityPacket, link);
cr.onConnectionReceived(link);
}
}
protected void connectionLost(BaseLink link) {
/**
* To be called from the child classes when a link to an existing device is disconnected
*/
public void onConnectionLost(BaseLink link) {
//Log.i("KDE/LinkProvider", "connectionLost");
for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionLost(link);
}
}
//To override
public abstract void onStart();
public abstract void onStop();
public abstract void onNetworkChange();

View File

@@ -17,6 +17,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.NetworkPacket;
import java.io.IOException;
@@ -34,6 +35,7 @@ public class BluetoothLink extends BaseLink {
private final OutputStream output;
private final BluetoothDevice remoteAddress;
private final BluetoothLinkProvider linkProvider;
private final DeviceInfo deviceInfo;
private boolean continueAccepting = true;
@@ -93,11 +95,12 @@ public class BluetoothLink extends BaseLink {
}
});
public BluetoothLink(Context context, ConnectionMultiplexer connection, InputStream input, OutputStream output, BluetoothDevice remoteAddress, String deviceId, BluetoothLinkProvider linkProvider) {
super(context, deviceId, linkProvider);
public BluetoothLink(Context context, ConnectionMultiplexer connection, InputStream input, OutputStream output, BluetoothDevice remoteAddress, DeviceInfo deviceInfo, BluetoothLinkProvider linkProvider) {
super(context, linkProvider);
this.connection = connection;
this.input = input;
this.output = output;
this.deviceInfo = deviceInfo;
this.remoteAddress = remoteAddress;
this.linkProvider = linkProvider;
}
@@ -111,6 +114,11 @@ public class BluetoothLink extends BaseLink {
return "BluetoothLink";
}
@Override
public DeviceInfo getDeviceInfo() {
return deviceInfo;
}
public void disconnect() {
if (connection == null) {
return;
@@ -120,7 +128,7 @@ public class BluetoothLink extends BaseLink {
connection.close();
} catch (IOException ignored) {
}
linkProvider.disconnectedLink(this, getDeviceId(), remoteAddress);
linkProvider.disconnectedLink(this, remoteAddress);
}
private void sendMessage(NetworkPacket np) throws JSONException, IOException {

View File

@@ -20,6 +20,8 @@ import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper;
import org.kde.kdeconnect.NetworkPacket;
@@ -44,7 +46,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
private static final int REQUEST_ENABLE_BT = 48;
private final Context context;
private final Map<String, BluetoothLink> visibleComputers = new HashMap<>();
private final Map<String, BluetoothLink> visibleDevices = new HashMap<>();
private final Map<BluetoothDevice, BluetoothSocket> sockets = new HashMap<>();
private final BluetoothAdapter bluetoothAdapter;
@@ -54,19 +56,16 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
String deviceId = identityPacket.getString("deviceId");
String certificateString = identityPacket.getString("certificate");
byte[] certificateBytes = Base64.decode(certificateString, 0);
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
BluetoothLink oldLink = visibleComputers.get(deviceId);
BluetoothLink oldLink = visibleDevices.get(deviceId);
if (oldLink == link) {
Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!");
return;
}
visibleComputers.put(deviceId, link);
connectionAccepted(deviceId, certificate, identityPacket, link);
visibleDevices.put(deviceId, link);
onConnectionReceived(link);
link.startListening();
link.packetReceived(identityPacket);
if (oldLink != null) {
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
oldLink.disconnect();
@@ -127,10 +126,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
return "BluetoothLinkProvider";
}
public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothDevice remoteAddress) {
public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) {
sockets.remove(remoteAddress);
visibleComputers.remove(deviceId);
connectionLost(link);
visibleDevices.remove(link.getDeviceId());
onConnectionLost(link);
}
private class ServerRunnable implements Runnable {
@@ -196,8 +195,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
OutputStream outputStream = connection.getDefaultOutputStream();
InputStream inputStream = connection.getDefaultInputStream();
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
NetworkPacket np = myDeviceInfo.toIdentityPacket();
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
outputStream.write(message);
outputStream.flush();
@@ -223,9 +224,15 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
Log.i("BTLinkProvider/Server", "Received identity packet");
String certificateString = identityPacket.getString("certificate");
byte[] certificateBytes = Base64.decode(certificateString, 0);
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
BluetoothLink link = new BluetoothLink(context, connection,
inputStream, outputStream, socket.getRemoteDevice(),
identityPacket.getString("deviceId"), BluetoothLinkProvider.this);
deviceInfo, BluetoothLinkProvider.this);
addLink(identityPacket, link);
} catch (Exception e) {
synchronized (sockets) {
@@ -360,23 +367,31 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
Log.i("BTLinkProvider/Client", "Received identity packet");
String myId = NetworkPacket.createIdentityPacket(context).getString("deviceId");
String myId = DeviceHelper.getDeviceId(context);
if (identityPacket.getString("deviceId").equals(myId)) {
// Probably won't happen, but just to be safe
connection.close();
return;
}
if (visibleComputers.containsKey(identityPacket.getString("deviceId"))) {
if (visibleDevices.containsKey(identityPacket.getString("deviceId"))) {
return;
}
Log.i("BTLinkProvider/Client", "identity packet received, creating link");
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
socket.getRemoteDevice(), identityPacket.getString("deviceId"), BluetoothLinkProvider.this);
String certificateString = identityPacket.getString("certificate");
byte[] certificateBytes = Base64.decode(certificateString, 0);
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
socket.getRemoteDevice(), deviceInfo, BluetoothLinkProvider.this);
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
NetworkPacket np2 = myDeviceInfo.toIdentityPacket();
np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
NetworkPacket np2 = NetworkPacket.createIdentityPacket(context);
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
@Override
public void onSuccess() {

View File

@@ -14,7 +14,9 @@ import androidx.annotation.WorkerThread;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper;
import org.kde.kdeconnect.NetworkPacket;
@@ -37,17 +39,13 @@ import kotlin.text.Charsets;
public class LanLink extends BaseLink {
public interface LinkDisconnectedCallback {
void linkDisconnected(LanLink brokenLink);
}
public enum ConnectionStarted {
Locally, Remotely
}
private volatile SSLSocket socket = null;
private final DeviceInfo deviceInfo;
private final LinkDisconnectedCallback callback;
private volatile SSLSocket socket = null;
@Override
public void disconnect() {
@@ -96,7 +94,7 @@ public class LanLink extends BaseLink {
boolean thereIsaANewSocket = (newSocket != socket);
if (!thereIsaANewSocket) {
Log.i("LanLink", "Socket closed and there's no new socket, disconnecting device");
callback.linkDisconnected(LanLink.this);
getLinkProvider().onConnectionLost(LanLink.this);
}
}
});
@@ -104,19 +102,22 @@ public class LanLink extends BaseLink {
return oldSocket;
}
public LanLink(Context context, String deviceId, LanLinkProvider linkProvider, SSLSocket socket) throws IOException {
super(context, deviceId, linkProvider);
callback = linkProvider;
public LanLink(@NonNull Context context, @NonNull DeviceInfo deviceInfo, @NonNull BaseLinkProvider linkProvider, @NonNull SSLSocket socket) throws IOException {
super(context, linkProvider);
this.deviceInfo = deviceInfo;
reset(socket);
}
@Override
public String getName() {
return "LanLink";
}
//Blocking, do not call from main thread
@Override
public DeviceInfo getDeviceInfo() {
return deviceInfo;
}
@WorkerThread
@Override
public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) {
@@ -172,9 +173,7 @@ public class LanLink extends BaseLink {
}
return true;
} catch (Exception e) {
if (callback != null) {
callback.onFailure(e);
}
callback.onFailure(e);
return false;
} finally {
//Make sure we close the payload stream, if any

View File

@@ -11,9 +11,13 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.WorkerThread;
import org.json.JSONException;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper;
@@ -33,11 +37,11 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.List;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
@@ -45,22 +49,24 @@ import javax.net.ssl.SSLSocket;
import kotlin.text.Charsets;
/**
* This BaseLinkProvider creates {@link LanLink}s to other devices on the same
* This LanLinkProvider creates {@link LanLink}s to other devices on the same
* WiFi network. The first packet sent over a socket must be an
* {@link NetworkPacket#createIdentityPacket(Context)}.
* {@link DeviceInfo#toIdentityPacket()}.
*
* @see #identityPacketReceived(NetworkPacket, Socket, LanLink.ConnectionStarted)
*/
public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDisconnectedCallback {
public class LanLinkProvider extends BaseLinkProvider {
private final static int UDP_PORT = 1716;
private final static int MIN_PORT = 1716;
private final static int MAX_PORT = 1764;
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
final static int MAX_UDP_PACKET_SIZE = 1024 * 512;
private final Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<>(); //Links by device id
private final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
private ServerSocket tcpServer;
private DatagramSocket udpServer;
@@ -70,18 +76,15 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
private boolean listening = false;
// To prevent infinte loop between Android < IceCream because both device can only broadcast identity packet but cannot connect via TCP
private final ArrayList<InetAddress> reverseConnectionBlackList = new ArrayList<>();
@Override // SocketClosedCallback
public void linkDisconnected(LanLink brokenLink) {
String deviceId = brokenLink.getDeviceId();
visibleComputers.remove(deviceId);
connectionLost(brokenLink);
public void onConnectionLost(BaseLink link) {
String deviceId = link.getDeviceId();
visibleDevices.remove(deviceId);
super.onConnectionLost(link);
}
//They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity.
private void tcpPacketReceived(Socket socket) {
//They received my UDP broadcast and are connecting to me. The first thing they send should be their identity packet.
@WorkerThread
private void tcpPacketReceived(Socket socket) throws IOException {
NetworkPacket networkPacket;
try {
@@ -104,57 +107,45 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}
//I've received their broadcast and should connect to their TCP socket and send my identity.
private void udpPacketReceived(DatagramPacket packet) {
@WorkerThread
private void udpPacketReceived(DatagramPacket packet) throws JSONException, IOException {
final InetAddress address = packet.getAddress();
try {
String message = new String(packet.getData(), Charsets.UTF_8);
final NetworkPacket identityPacket = NetworkPacket.unserialize(message);
final String deviceId = identityPacket.getString("deviceId");
if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity packet");
String message = new String(packet.getData(), Charsets.UTF_8);
final NetworkPacket identityPacket = NetworkPacket.unserialize(message);
final String deviceId = identityPacket.getString("deviceId");
if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
Log.e("KDE/LanLinkProvider", "Expecting an UDP identity packet");
return;
} else {
String myId = DeviceHelper.getDeviceId(context);
if (deviceId.equals(myId)) {
//Ignore my own broadcast
return;
} else {
String myId = DeviceHelper.getDeviceId(context);
if (deviceId.equals(myId)) {
//Ignore my own broadcast
return;
}
}
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket = socketFactory.createSocket(address, tcpPort);
configureSocket(socket);
OutputStream out = socket.getOutputStream();
NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context);
out.write(myIdentity.serialize().getBytes());
out.flush();
identityPacketReceived(identityPacket, socket, LanLink.ConnectionStarted.Remotely);
} catch (Exception e) {
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address, e);
if (!reverseConnectionBlackList.contains(address)) {
Log.w("KDE/LanLinkProvider", "Blacklisting " + address);
reverseConnectionBlackList.add(address);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
reverseConnectionBlackList.remove(address);
}
}, 5 * 1000);
// Try to cause a reverse connection
onNetworkChange();
}
}
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
if (tcpPort < MIN_PORT || tcpPort > MAX_PORT) {
Log.e("LanLinkProvider", "TCP port outside of kdeconnect's range");
return;
}
SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket = socketFactory.createSocket(address, tcpPort);
configureSocket(socket);
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
NetworkPacket myIdentity = myDeviceInfo.toIdentityPacket();
OutputStream out = socket.getOutputStream();
out.write(myIdentity.serialize().getBytes());
out.flush();
identityPacketReceived(identityPacket, socket, LanLink.ConnectionStarted.Remotely);
}
private void configureSocket(Socket socket) {
@@ -169,6 +160,8 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
* Called when a new 'identity' packet is received. Those are passed here by
* {@link #tcpPacketReceived(Socket)} and {@link #udpPacketReceived(DatagramPacket)}.
* <p>
* Should be called on a new thread since it blocks until the handshake is completed.
* </p><p>
* If the remote device should be connected, this calls {@link #addLink}.
* Otherwise, if there was an Exception, we unpair from that device.
* </p>
@@ -177,7 +170,8 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
* @param socket a new Socket, which should be used to receive packets from the remote device
* @param connectionStarted which side started this connection
*/
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted) {
@WorkerThread
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted) throws IOException {
String myId = DeviceHelper.getDeviceId(context);
final String deviceId = identityPacket.getString("deviceId");
@@ -189,88 +183,70 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
// If I'm the TCP server I will be the SSL client and viceversa.
final boolean clientMode = (connectionStarted == LanLink.ConnectionStarted.Locally);
// Do the SSL handshake
try {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
//Device paired with and old version, we can't use it as we lack the certificate
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
//Device paired with and old version, we can't use it as we lack the certificate
Device device = KdeConnect.getInstance().getDevice(deviceId);
if (device == null) {
return;
}
device.unpair();
//Retry as unpaired
identityPacketReceived(identityPacket, socket, connectionStarted);
}
String deviceName = identityPacket.getString("deviceName", "unknown");
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + deviceName + " trusted:" + isDeviceTrusted);
final SSLSocket sslSocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
sslSocket.addHandshakeCompletedListener(event -> {
String mode = clientMode ? "client" : "server";
try {
Certificate certificate = event.getPeerCertificates()[0];
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + deviceName + " secured with " + event.getCipherSuite());
addLink(sslSocket, deviceInfo);
} catch (IOException e) {
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + deviceName, e);
Device device = KdeConnect.getInstance().getDevice(deviceId);
if (device == null) {
return;
}
device.unpair();
//Retry as unpaired
identityPacketReceived(identityPacket, socket, connectionStarted);
}
});
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
sslsocket.addHandshakeCompletedListener(event -> {
String mode = clientMode ? "client" : "server";
try {
Certificate certificate = event.getPeerCertificates()[0];
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
addLink(deviceId, certificate, identityPacket, sslsocket);
} catch (Exception e) {
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
Device device = KdeConnect.getInstance().getDevice(deviceId);
if (device == null) {
return;
}
device.unpair();
}
});
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
ThreadHelper.execute(() -> {
try {
synchronized (this) {
sslsocket.startHandshake();
}
} catch (Exception e) {
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPacket.getString("deviceName"), e);
//String[] ciphers = sslsocket.getSupportedCipherSuites();
//for (String cipher : ciphers) {
// Log.i("SupportedCiphers","cipher: " + cipher);
//}
}
});
} catch (Exception e) {
Log.e("LanLink", "Exception", e);
}
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
Log.d("LanLinkProvider", "Starting handshake");
sslSocket.startHandshake();
Log.d("LanLinkProvider", "Handshake done");
}
/**
* Add or update a link in the {@link #visibleComputers} map. This method is synchronized, which ensures that only one
* link is operated on at a time.
* <p>
* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* </p>
* Add or update a link in the {@link #visibleDevices} map.
*
* @param deviceId remote device id
* @param certificate remote device certificate
* @param identityPacket identity packet with the remote device's device name, type, protocol version, etc.
* @param socket a new Socket, which should be used to send and receive packets from the remote device
* @param deviceInfo remote device info
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket)}
*/
private void addLink(String deviceId, Certificate certificate, final NetworkPacket identityPacket, SSLSocket socket) throws IOException {
LanLink currentLink = visibleComputers.get(deviceId);
if (currentLink != null) {
//Update old link
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
final Socket oldSocket = currentLink.reset(socket);
//Log.e("KDE/LanLinkProvider", "Replacing socket. old: "+ oldSocket.hashCode() + " - new: "+ socket.hashCode());
private void addLink(SSLSocket socket, DeviceInfo deviceInfo) throws IOException {
LanLink link = visibleDevices.get(deviceInfo.id);
if (link != null) {
if (!link.getDeviceInfo().certificate.equals(deviceInfo.certificate)) {
Log.e("LanLinkProvider", "LanLink was asked to replace a socket but the certificate doesn't match, aborting");
return;
}
// Update existing link
Log.d("KDE/LanLinkProvider", "Reusing same link for device " + deviceInfo.id);
final Socket oldSocket = link.reset(socket);
} else {
Log.i("KDE/LanLinkProvider", "Creating a new link for device " + deviceId);
//Let's create the link
LanLink link = new LanLink(context, deviceId, this, socket);
visibleComputers.put(deviceId, link);
connectionAccepted(deviceId, certificate, identityPacket, link);
// Create a new link
Log.d("KDE/LanLinkProvider", "Creating a new link for device " + deviceInfo.id);
link = new LanLink(context, deviceInfo, this, socket);
visibleDevices.put(deviceInfo.id, link);
onConnectionReceived(link);
}
}
@@ -296,14 +272,19 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
ThreadHelper.execute(() -> {
Log.i("UdpListener", "Starting UDP listener");
while (listening) {
final int bufferSize = 1024 * 512;
byte[] data = new byte[bufferSize];
DatagramPacket packet = new DatagramPacket(data, bufferSize);
try {
DatagramPacket packet = new DatagramPacket(new byte[MAX_UDP_PACKET_SIZE], MAX_UDP_PACKET_SIZE);
udpServer.receive(packet);
udpPacketReceived(packet);
} catch (Exception e) {
ThreadHelper.execute(() -> {
try {
udpPacketReceived(packet);
} catch (JSONException | IOException e) {
Log.e("LanLinkProvider", "Exception receiving incoming UDP connection", e);
}
});
} catch (IOException e) {
Log.e("LanLinkProvider", "UdpReceive exception", e);
onNetworkChange(); // Trigger a UDP broadcast to try to get them to connect to us instead
}
}
Log.w("UdpListener", "Stopping UDP listener");
@@ -322,7 +303,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
try {
Socket socket = tcpServer.accept();
configureSocket(socket);
tcpPacketReceived(socket);
ThreadHelper.execute(() -> {
try {
tcpPacketReceived(socket);
} catch (IOException e) {
Log.e("LanLinkProvider", "Exception receiving incoming TCP connection", e);
}
});
} catch (Exception e) {
Log.e("LanLinkProvider", "TcpReceive exception", e);
}
@@ -351,7 +338,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
throw new RuntimeException("This should not be reachable");
}
private void broadcastUdpPacket() {
private void broadcastUdpIdentityPacket() {
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
Log.i("LanLinkProvider", "broadcastUdpPacket: relax cowboy");
return;
@@ -359,57 +346,73 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
lastBroadcast = System.currentTimeMillis();
ThreadHelper.execute(() -> {
ArrayList<String> iplist = CustomDevicesActivity
List<String> ipStringList = CustomDevicesActivity
.getCustomDeviceList(PreferenceManager.getDefaultSharedPreferences(context));
if (TrustedNetworkHelper.isTrustedNetwork(context)) {
iplist.add("255.255.255.255"); //Default: broadcast.
ipStringList.add("255.255.255.255"); //Default: broadcast.
} else {
Log.i("LanLinkProvider", "Current network isn't trusted, not broadcasting");
}
if (iplist.isEmpty()) {
return;
}
NetworkPacket identity = NetworkPacket.createIdentityPacket(context);
if (tcpServer == null || !tcpServer.isBound()) {
Log.i("LanLinkProvider", "Won't broadcast UDP packet if TCP socket is not ready yet");
return;
}
int port = tcpServer.getLocalPort();
identity.set("tcpPort", port);
DatagramSocket socket = null;
byte[] bytes = null;
try {
socket = new DatagramSocket();
socket.setReuseAddress(true);
socket.setBroadcast(true);
bytes = identity.serialize().getBytes(Charsets.UTF_8);
} catch (Exception e) {
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket", e);
}
if (bytes != null) {
Log.i("KDE/LanLinkProvider","Sending broadcast to "+iplist.size()+" ips");
for (String ipstr : iplist) {
try {
InetAddress client = InetAddress.getByName(ipstr);
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
//Log.i("KDE/LanLinkProvider","Udp identity packet sent to address "+client);
} catch (Exception e) {
Log.e("KDE/LanLinkProvider", "Sending udp identity packet failed. Invalid address? (" + ipstr + ")", e);
}
ArrayList<InetAddress> ipList = new ArrayList<>();
for (String ip : ipStringList) {
try {
ipList.add(InetAddress.getByName(ip));
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
if (socket != null) {
socket.close();
if (ipList.isEmpty()) {
return;
}
sendUdpIdentityPacket(ipList);
});
}
@WorkerThread
public void sendUdpIdentityPacket(List<InetAddress> ipList) {
if (tcpServer == null || !tcpServer.isBound()) {
Log.i("LanLinkProvider", "Won't broadcast UDP packet if TCP socket is not ready yet");
return;
}
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
NetworkPacket identity = myDeviceInfo.toIdentityPacket();
identity.set("tcpPort", tcpServer.getLocalPort());
byte[] bytes;
try {
bytes = identity.serialize().getBytes(Charsets.UTF_8);
} catch (JSONException e) {
Log.e("KDE/LanLinkProvider", "Failed to serialize identity packet", e);
return;
}
DatagramSocket socket;
try {
socket = new DatagramSocket();
socket.setReuseAddress(true);
socket.setBroadcast(true);
} catch (SocketException e) {
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket", e);
return;
}
for (InetAddress ip : ipList) {
try {
socket.send(new DatagramPacket(bytes, bytes.length, ip, MIN_PORT));
//Log.i("KDE/LanLinkProvider","Udp identity packet sent to address "+client);
} catch (IOException e) {
Log.e("KDE/LanLinkProvider", "Sending udp identity packet failed. Invalid address? (" + ip.toString() + ")", e);
}
}
socket.close();
}
@Override
public void onStart() {
//Log.i("KDE/LanLinkProvider", "onStart");
@@ -420,13 +423,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
setupUdpListener();
setupTcpListener();
broadcastUdpPacket();
broadcastUdpIdentityPacket();
}
}
@Override
public void onNetworkChange() {
broadcastUdpPacket();
broadcastUdpIdentityPacket();
}
@Override

View File

@@ -14,12 +14,14 @@ import androidx.annotation.WorkerThread;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.NetworkPacket;
public class LoopbackLink extends BaseLink {
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
super(context, "loopback", linkProvider);
super(context, linkProvider);
}
@Override
@@ -40,4 +42,9 @@ public class LoopbackLink extends BaseLink {
return true;
}
@Override
public DeviceInfo getDeviceInfo() {
return DeviceHelper.getDeviceInfo(context);
}
}

View File

@@ -9,9 +9,6 @@ package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.content.Context;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPacket;
public class LoopbackLinkProvider extends BaseLinkProvider {
@@ -32,9 +29,8 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
@Override
public void onNetworkChange() {
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
String deviceId = DeviceHelper.getDeviceId(context);
connectionAccepted(deviceId, SslHelper.certificate, np, new LoopbackLink(context, this));
LoopbackLink link = new LoopbackLink(context, this);
onConnectionReceived(link);
}
@Override

View File

@@ -64,6 +64,8 @@ public class BackgroundService extends Service {
return instance;
}
private static boolean initialized = false;
// This indicates when connected over wifi/usb/bluetooth/(anything other than cellular)
private final MutableLiveData<Boolean> connectedToNonCellularNetwork = new MutableLiveData<>();
public LiveData<Boolean> isConnectedToNonCellularNetwork() {
@@ -88,6 +90,10 @@ public class BackgroundService extends Service {
}
public void onNetworkChange() {
if (!initialized) {
Log.d("KDE/BackgroundService", "ignoring onNetworkChange called before the service is initialized");
return;
}
Log.d("KDE/BackgroundService", "onNetworkChange");
for (BaseLinkProvider a : linkProviders) {
a.onNetworkChange();
@@ -145,6 +151,7 @@ public class BackgroundService extends Service {
for (BaseLinkProvider a : linkProviders) {
a.onStart();
}
initialized = true;
}
private static NetworkRequest.Builder getNonCellularNetworkRequestBuilder() {
@@ -248,6 +255,7 @@ public class BackgroundService extends Service {
@Override
public void onDestroy() {
Log.d("KdeConnect/BgService", "onDestroy");
initialized = false;
for (BaseLinkProvider a : linkProviders) {
a.onStop();
}

View File

@@ -14,7 +14,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.AnyThread;
@@ -26,7 +25,6 @@ import androidx.core.content.ContextCompat;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.lang3.StringUtils;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
@@ -34,18 +32,12 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.kde.kdeconnect_tp.R;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -54,30 +46,26 @@ public class Device implements BaseLink.PacketReceiver {
private final Context context;
private final String deviceId;
private String name;
public Certificate certificate;
final DeviceInfo deviceInfo;
private int notificationId;
private int protocolVersion;
private DeviceType deviceType;
PairingHandler pairingHandler;
private final CopyOnWriteArrayList<PairingHandler.PairingCallback> pairingCallbacks = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
private DevicePacketQueue packetQueue;
private List<String> supportedPlugins = new ArrayList<>();
private List<String> supportedPlugins;
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
private final SharedPreferences settings;
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
private Set<String> incomingCapabilities = new HashSet<>();
public boolean supportsPacketType(String type) {
if (incomingCapabilities == null) {
if (deviceInfo.incomingCapabilities == null) {
return true;
} else {
return incomingCapabilities.contains(type);
return deviceInfo.incomingCapabilities.contains(type);
}
}
@@ -85,109 +73,52 @@ public class Device implements BaseLink.PacketReceiver {
void onPluginsChanged(@NonNull Device device);
}
public enum DeviceType {
Phone,
Tablet,
Computer,
Tv;
static public DeviceType FromString(String s) {
if ("tablet".equals(s)) return Tablet;
if ("phone".equals(s)) return Phone;
if ("tv".equals(s)) return Tv;
return Computer; //Default
}
@NonNull
public String toString() {
switch (this) {
case Tablet:
return "tablet";
case Phone:
return "phone";
case Tv:
return "tv";
default:
return "desktop";
}
}
public Drawable getIcon(Context context) {
int drawableId;
switch (this) {
case Phone:
drawableId = R.drawable.ic_device_phone_32dp;
break;
case Tablet:
drawableId = R.drawable.ic_device_tablet_32dp;
break;
case Tv:
drawableId = R.drawable.ic_device_tv_32dp;
break;
default:
drawableId = R.drawable.ic_device_laptop_32dp;
}
return ContextCompat.getDrawable(context, drawableId);
}
}
// Remembered trusted device, we need to wait for a incoming Link to communicate
Device(@NonNull Context context, @NonNull String deviceId) throws CertificateException {
/**
* Constructor for remembered, already-trusted devices.
* Given the deviceId, it will load the other properties from SharedPreferences.
*/
Device(@NonNull Context context, @NonNull String deviceId) {
this.context = context;
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings);
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
this.deviceId = deviceId;
this.name = settings.getString("deviceName", context.getString(R.string.unknown_device));
this.protocolVersion = 0; //We don't know it yet
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
this.certificate = SslHelper.getDeviceCertificate(context, deviceId);
Log.i("Device","Loading trusted device: " + this.name);
//Assume every plugin is supported until addLink is called and we can get the actual list
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
//Do not load plugins yet, the device is not present
//reloadPluginsFromSettings();
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
Log.i("Device","Loading trusted device: " + deviceInfo.name);
}
// Device known via an incoming connection sent to us via a Link, we don't trust it yet
Device(@NonNull Context context, @NonNull String deviceId, @NonNull Certificate certificate, @NonNull NetworkPacket identityPacket, @NonNull BaseLink dl) {
Log.i("Device","Creating untrusted device");
/**
* Constructor for devices discovered but not trusted yet.
* Gets the DeviceInfo by calling link.getDeviceInfo() on the link passed.
* This constructor also calls addLink() with the link you pass to it, since it's not legal to have an unpaired Device with 0 links.
*/
Device(@NonNull Context context, @NonNull BaseLink link) {
this.context = context;
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
this.deviceInfo = link.getDeviceInfo();
this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
this.deviceId = deviceId;
this.certificate = certificate;
// The following properties are read from the identityPacket in addLink since they can change in future identity packets
this.name = context.getString(R.string.unknown_device);
this.deviceType = DeviceType.Computer;
this.protocolVersion = 0;
addLink(identityPacket, dl);
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
Log.i("Device","Creating untrusted device: "+ deviceInfo.name);
addLink(link);
}
public String getName() {
return StringUtils.defaultString(name, context.getString(R.string.unknown_device));
return deviceInfo.name;
}
public Drawable getIcon() {
return deviceType.getIcon(context);
return deviceInfo.type.getIcon(context);
}
public DeviceType getDeviceType() {
return deviceType;
return deviceInfo.type;
}
public String getDeviceId() {
return deviceId;
return deviceInfo.id;
}
public Certificate getCertificate() {
return deviceInfo.certificate;
}
public Context getContext() {
@@ -196,7 +127,7 @@ public class Device implements BaseLink.PacketReceiver {
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() {
return protocolVersion - DeviceHelper.ProtocolVersion;
return deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion;
}
@@ -255,28 +186,26 @@ public class Device implements BaseLink.PacketReceiver {
@Override
public void pairingSuccessful() {
Log.i("Device", "pairing successful, adding to trusted devices list");
hidePairingNotification();
// Store current device certificate so we can check it in the future (TOFU)
SharedPreferences.Editor editor = context.getSharedPreferences(getDeviceId(), Context.MODE_PRIVATE).edit();
try {
String encodedCertificate = Base64.encodeToString(certificate.getEncoded(), 0);
editor.putString("certificate", encodedCertificate);
} catch(CertificateEncodingException e) {
throw new RuntimeException(e);
}
editor.putString("deviceName", name);
editor.putString("deviceType", deviceType.toString());
editor.apply();
deviceInfo.saveInSettings(Device.this.settings);
// Store as trusted device
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId, true).apply();
preferences.edit().putBoolean(deviceInfo.id, true).apply();
reloadPluginsFromSettings();
try {
reloadPluginsFromSettings();
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
cb.pairingSuccessful();
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
cb.pairingSuccessful();
}
} catch (Exception e) {
Log.e("PairingHandler", "Exception in pairingSuccessful. Not unpairing because saving the trusted device succeeded");
e.printStackTrace();
}
}
@@ -290,10 +219,11 @@ public class Device implements BaseLink.PacketReceiver {
@Override
public void unpaired() {
Log.i("Device", "unpaired, removing from trusted devices list");
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).apply();
preferences.edit().remove(deviceInfo.id).apply();
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
SharedPreferences devicePreferences = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
devicePreferences.edit().clear().apply();
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
@@ -335,7 +265,7 @@ public class Device implements BaseLink.PacketReceiver {
final NotificationManager notificationManager = ContextCompat.getSystemService(getContext(), NotificationManager.class);
String verificationKeyShort = SslHelper.getVerificationKey(SslHelper.certificate, certificate).substring(8);
String verificationKeyShort = SslHelper.getVerificationKey(SslHelper.certificate, deviceInfo.certificate).substring(8);
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
@@ -366,7 +296,7 @@ public class Device implements BaseLink.PacketReceiver {
return !links.isEmpty();
}
public void addLink(NetworkPacket identityPacket, BaseLink link) {
public void addLink(BaseLink link) {
if (links.isEmpty()) {
packetQueue = new DevicePacketQueue(this);
}
@@ -374,33 +304,11 @@ public class Device implements BaseLink.PacketReceiver {
links.add(link);
link.addPacketReceiver(this);
this.protocolVersion = identityPacket.getInt("protocolVersion");
boolean hasChanges = updateDeviceInfo(link.getDeviceInfo());
if (identityPacket.has("deviceName")) {
this.name = identityPacket.getString("deviceName", this.name);
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", this.name);
editor.apply();
if (hasChanges || links.size() == 1) {
reloadPluginsFromSettings();
}
if (identityPacket.has("deviceType")) {
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
}
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
if (incomingCapabilities != null && outgoingCapabilities != null) {
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
} else {
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
}
this.incomingCapabilities = incomingCapabilities;
reloadPluginsFromSettings();
}
public void removeLink(BaseLink link) {
@@ -418,6 +326,30 @@ public class Device implements BaseLink.PacketReceiver {
}
}
public boolean updateDeviceInfo(@NonNull DeviceInfo newDeviceInfo) {
boolean hasChanges = false;
if (!deviceInfo.name.equals(newDeviceInfo.name) || deviceInfo.type != newDeviceInfo.type) {
hasChanges = true;
deviceInfo.name = newDeviceInfo.name;
deviceInfo.type = newDeviceInfo.type;
if (isPaired()) {
deviceInfo.saveInSettings(settings);
}
}
if (deviceInfo.outgoingCapabilities != newDeviceInfo.outgoingCapabilities ||
deviceInfo.incomingCapabilities != newDeviceInfo.incomingCapabilities) {
if (newDeviceInfo.outgoingCapabilities != null && newDeviceInfo.incomingCapabilities != null) {
hasChanges = true;
Log.i("updateDeviceInfo", "Updating supported plugins according to new capabilities");
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(newDeviceInfo.incomingCapabilities, newDeviceInfo.outgoingCapabilities));
}
}
return hasChanges;
}
@Override
public void onPacketReceived(@NonNull NetworkPacket np) {
@@ -581,7 +513,7 @@ public class Device implements BaseLink.PacketReceiver {
}
if (!success) {
Log.e("KDE/sendPacket", "No device link (of " + links.size() + " available) could send the packet. Packet " + np.getType() + " to " + name + " lost!");
Log.e("KDE/sendPacket", "No device link (of " + links.size() + " available) could send the packet. Packet " + np.getType() + " to " + deviceInfo.name + " lost!");
}
return success;
@@ -616,16 +548,16 @@ public class Device implements BaseLink.PacketReceiver {
if (existing != null) {
if (!existing.isCompatible()) {
Log.i("KDE/addPlugin", "Minimum requirements (e.g. API level) not fulfilled " + pluginKey);
Log.d("KDE/addPlugin", "Minimum requirements (e.g. API level) not fulfilled " + pluginKey);
return false;
}
//Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
if (existing.checkOptionalPermissions()) {
Log.i("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
Log.d("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
pluginsWithoutOptionalPermissions.remove(pluginKey);
} else {
Log.e("KDE/addPlugin", "No optional permission " + pluginKey);
Log.d("KDE/addPlugin", "No optional permission " + pluginKey);
pluginsWithoutOptionalPermissions.put(pluginKey, existing);
}
return true;
@@ -638,25 +570,24 @@ public class Device implements BaseLink.PacketReceiver {
}
if (!plugin.isCompatible()) {
Log.i("KDE/addPlugin", "Minimum requirements (e.g. API level) not fulfilled " + pluginKey);
Log.d("KDE/addPlugin", "Minimum requirements (e.g. API level) not fulfilled " + pluginKey);
return false;
}
plugins.put(pluginKey, plugin);
if (!plugin.checkRequiredPermissions()) {
Log.e("KDE/addPlugin", "No permission " + pluginKey);
Log.d("KDE/addPlugin", "No permission " + pluginKey);
plugins.remove(pluginKey);
pluginsWithoutPermissions.put(pluginKey, plugin);
return false;
} else {
Log.i("KDE/addPlugin", "Permissions OK " + pluginKey);
Log.d("KDE/addPlugin", "Permissions OK " + pluginKey);
plugins.put(pluginKey, plugin);
pluginsWithoutPermissions.remove(pluginKey);
if (plugin.checkOptionalPermissions()) {
Log.i("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
Log.d("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
pluginsWithoutOptionalPermissions.remove(pluginKey);
} else {
Log.e("KDE/addPlugin", "No optional permission " + pluginKey);
Log.d("KDE/addPlugin", "No optional permission " + pluginKey);
pluginsWithoutOptionalPermissions.put(pluginKey, plugin);
}
}
@@ -698,6 +629,7 @@ public class Device implements BaseLink.PacketReceiver {
}
public void reloadPluginsFromSettings() {
Log.i("Device", deviceInfo.name +": reloading plugins");
MultiValuedMap<String, String> newPluginsByIncomingInterface = new ArrayListValuedHashMap<>();
for (String pluginKey : supportedPlugins) {

View File

@@ -0,0 +1,133 @@
/*
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect
import android.content.Context
import android.content.SharedPreferences
import android.graphics.drawable.Drawable
import android.util.Base64
import androidx.core.content.ContextCompat
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
import org.kde.kdeconnect_tp.R
import java.security.cert.Certificate
import java.security.cert.CertificateEncodingException
/**
* DeviceInfo contains all the properties needed to instantiate a Device.
*/
class DeviceInfo(
@JvmField val id : String,
@JvmField val certificate : Certificate,
@JvmField var name : String,
@JvmField var type : DeviceType,
@JvmField var protocolVersion : Int = 0,
@JvmField var incomingCapabilities : Set<String>? = null,
@JvmField var outgoingCapabilities : Set<String>? = null,
) {
/**
* Saves the info in settings so it can be restored later using loadFromSettings().
* This is used to keep info from paired devices, even when they are not reachable.
* The capabilities and protocol version are not persisted.
*/
fun saveInSettings(settings: SharedPreferences) {
val editor = settings.edit()
try {
val encodedCertificate = Base64.encodeToString(certificate.encoded, 0)
editor.putString("certificate", encodedCertificate)
} catch (e: CertificateEncodingException) {
throw RuntimeException(e)
}
editor.putString("deviceName", name)
editor.putString("deviceType", type.toString())
editor.apply()
}
/**
* Serializes to a NetworkPacket, which LanLinkProvider uses to send this data over the network.
* The serialization doesn't include the certificate, since LanLink can query that from the socket.
* Can be deserialized using fromIdentityPacketAndCert(), given a certificate.
*/
fun toIdentityPacket(): NetworkPacket {
val np = NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY)
np.set("deviceId", id)
np.set("deviceName", name)
np.set("protocolVersion", protocolVersion)
np.set("deviceType", type.toString())
np.set("incomingCapabilities", incomingCapabilities)
np.set("outgoingCapabilities", outgoingCapabilities)
return np
}
companion object {
/**
* Recreates a DeviceInfo object that was persisted using saveInSettings()
*/
@JvmStatic
fun loadFromSettings(context : Context, deviceId: String, settings: SharedPreferences): DeviceInfo {
val deviceName = settings.getString("deviceName", "unknown")!!
val deviceType = DeviceType.fromString(settings.getString("deviceType", "desktop")!!)
val certificate = SslHelper.getDeviceCertificate(context, deviceId)
return DeviceInfo(id = deviceId, name = deviceName, type = deviceType, certificate = certificate)
}
/**
* Recreates a DeviceInfo object that was serialized using toIdentityPacket().
* Since toIdentityPacket() doesn't serialize the certificate, this needs to be passed separately.
*/
@JvmStatic
fun fromIdentityPacketAndCert(identityPacket : NetworkPacket, certificate : Certificate): DeviceInfo {
val deviceId = identityPacket.getString("deviceId")
val deviceName = identityPacket.getString("deviceName", "unknown")
val protocolVersion = identityPacket.getInt("protocolVersion")
val deviceType = DeviceType.fromString(identityPacket.getString("deviceType", "desktop"))
val incomingCapabilities = identityPacket.getStringSet("incomingCapabilities")
val outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities")
return DeviceInfo(id = deviceId, name = deviceName, type = deviceType, certificate = certificate,
protocolVersion = protocolVersion, incomingCapabilities = incomingCapabilities, outgoingCapabilities = outgoingCapabilities)
}
}
}
enum class DeviceType {
Phone, Tablet, Computer, Tv;
override fun toString(): String {
return when (this) {
Tablet -> "tablet"
Phone -> "phone"
Tv -> "tv"
else -> "desktop"
}
}
fun getIcon(context: Context): Drawable? {
val drawableId: Int = when (this) {
Phone -> R.drawable.ic_device_phone_32dp
Tablet -> R.drawable.ic_device_tablet_32dp
Tv -> R.drawable.ic_device_tv_32dp
else -> R.drawable.ic_device_laptop_32dp
}
return ContextCompat.getDrawable(context, drawableId)
}
companion object {
@JvmStatic
fun fromString(s: String): DeviceType {
return when (s) {
"phone" -> Phone
"tablet" -> Tablet
"tv" -> Tv
else -> Computer
}
}
}
}

View File

@@ -19,7 +19,10 @@ import android.util.Log;
import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceInfo;
import org.kde.kdeconnect.DeviceType;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.PluginFactory;
import java.io.BufferedReader;
import java.io.IOException;
@@ -53,13 +56,13 @@ public class DeviceHelper {
return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
}
public static Device.DeviceType getDeviceType(Context context) {
public static DeviceType getDeviceType(Context context) {
if (isTv(context)) {
return Device.DeviceType.Tv;
return DeviceType.Tv;
} else if (isTablet()) {
return Device.DeviceType.Tablet;
return DeviceType.Tablet;
} else {
return Device.DeviceType.Phone;
return DeviceType.Phone;
}
}
@@ -146,4 +149,14 @@ public class DeviceHelper {
return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null);
}
public static DeviceInfo getDeviceInfo(Context context) {
return new DeviceInfo(getDeviceId(context),
SslHelper.certificate,
getDeviceName(context),
DeviceHelper.getDeviceType(context),
ProtocolVersion,
PluginFactory.getIncomingCapabilities(),
PluginFactory.getOutgoingCapabilities());
}
}

View File

@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
*/
package org.kde.kdeconnect;
@@ -22,11 +22,8 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.kde.kdeconnect.UserInterface.ThemeUtil;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -77,6 +74,7 @@ public class KdeConnect extends Application {
}
private void onDeviceListChanged() {
Log.i("MainActivity","Device list changed, notifying "+ deviceListChangedCallbacks.size() +" observers.");
for (DeviceListChangedCallback callback : deviceListChangedCallbacks.values()) {
callback.onDeviceListChanged();
}
@@ -115,14 +113,9 @@ public class KdeConnect extends Application {
for (String deviceId : trustedDevices) {
//Log.e("BackgroundService", "Loading device "+deviceId);
if (preferences.getBoolean(deviceId, false)) {
try {
Device device = new Device(this, deviceId);
devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback);
} catch (CertificateException e) {
Log.e("KdeConnect", "Could not load trusted device, certificate not valid: " + deviceId);
e.printStackTrace();
}
Device device = new Device(this, deviceId);
devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback);
}
}
}
@@ -151,18 +144,13 @@ public class KdeConnect extends Application {
private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onConnectionReceived(@NonNull final String deviceId,
@NonNull final Certificate certificate,
@NonNull final NetworkPacket identityPacket,
@NonNull final BaseLink link) {
Device device = devices.get(deviceId);
public void onConnectionReceived(@NonNull final BaseLink link) {
Device device = devices.get(link.getDeviceId());
if (device != null) {
Log.i("KDE/Application", "addLink, known device: " + deviceId);
device.addLink(identityPacket, link);
device.addLink(link);
} else {
Log.i("KDE/Application", "addLink,unknown device: " + deviceId);
device = new Device(KdeConnect.this, deviceId, certificate, identityPacket, link);
devices.put(deviceId, device);
device = new Device(KdeConnect.this, link);
devices.put(link.getDeviceId(), device);
device.addPairingCallback(devicePairingCallback);
}
onDeviceListChanged();
@@ -170,21 +158,22 @@ public class KdeConnect extends Application {
@Override
public void onConnectionLost(BaseLink link) {
Device d = devices.get(link.getDeviceId());
Device device = devices.get(link.getDeviceId());
Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
if (d != null) {
d.removeLink(link);
if (!d.isReachable() && !d.isPaired()) {
if (device != null) {
device.removeLink(link);
if (!device.isReachable() && !device.isPaired()) {
//Log.e("onConnectionLost","Removing connection device because it was not paired");
devices.remove(link.getDeviceId());
d.removePairingCallback(devicePairingCallback);
device.removePairingCallback(devicePairingCallback);
}
} else {
//Log.d("KDE/onConnectionLost","Removing connection to unknown device");
Log.d("KDE/onConnectionLost","Removing connection to unknown device");
}
onDeviceListChanged();
}
};
public BaseLinkProvider.ConnectionReceiver getConnectionListener() {
return connectionListener;
}

View File

@@ -6,15 +6,10 @@
package org.kde.kdeconnect;
import android.content.Context;
import android.util.Log;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Plugins.PluginFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -167,7 +162,7 @@ public class NetworkPacket {
}
}
private Set<String> getStringSet(String key) {
public Set<String> getStringSet(String key) {
JSONArray jsonArray = mBody.optJSONArray(key);
if (jsonArray == null) return null;
Set<String> list = new HashSet<>();
@@ -263,26 +258,6 @@ public class NetworkPacket {
return np;
}
static public NetworkPacket createIdentityPacket(Context context) {
NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
String deviceId = DeviceHelper.getDeviceId(context);
try {
np.mBody.put("deviceId", deviceId);
np.mBody.put("deviceName", DeviceHelper.getDeviceName(context));
np.mBody.put("protocolVersion", DeviceHelper.ProtocolVersion);
np.mBody.put("deviceType", DeviceHelper.getDeviceType(context).toString());
np.mBody.put("incomingCapabilities", new JSONArray(PluginFactory.getIncomingCapabilities()));
np.mBody.put("outgoingCapabilities", new JSONArray(PluginFactory.getOutgoingCapabilities()));
} catch (Exception e) {
Log.e("NetworkPacket", "Exception on createIdentityPacket", e);
}
return np;
}
public void setPayload(Payload payload) { mPayload = payload; }
public Payload getPayload() {

View File

@@ -4,12 +4,10 @@
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface;
package org.kde.kdeconnect;
import android.util.Log;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect_tp.R;
import java.util.Timer;
@@ -62,10 +60,15 @@ public class PairingHandler {
Log.w("PairingHandler", "Ignoring second pairing request before the first one timed out");
break;
case Paired:
Log.w("PairingHandler", "Auto-accepting pairing request from a device we already trusted");
acceptPairing();
break;
case NotPaired:
if (mPairState == PairState.Paired) {
Log.w("PairingHandler", "Received pairing request from a device we already trusted.");
// It would be nice to auto-accept the pairing request here, but since the pairing accept and pairing request
// messages are identical, this could create an infinite loop if both devices are "accepting" each other pairs.
// Instead, unpair and handle as if "NotPaired".
mPairState = PairState.NotPaired;
mCallback.unpaired();
}
mPairState = PairState.RequestedByPeer;
mPairingTimer = new Timer();
@@ -89,13 +92,14 @@ public class PairingHandler {
break;
case Requested: // We started pairing and got rejected
case RequestedByPeer: // They stared pairing, then cancelled
mPairState = PairState.NotPaired;
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
break;
case Paired:
mPairState = PairState.NotPaired;
mCallback.unpaired();
break;
}
mPairState = PairState.NotPaired;
}
}
@@ -191,9 +195,11 @@ public class PairingHandler {
public void unpair() {
mPairState = PairState.NotPaired;
NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
np.set("pair", false);
mDevice.sendPacket(np);
if (mDevice.isReachable()) {
NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
np.set("pair", false);
mDevice.sendPacket(np);
}
mCallback.unpaired();
}

View File

@@ -19,7 +19,7 @@ import android.view.KeyEvent;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceType;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
@@ -33,7 +33,7 @@ public class BigscreenPlugin extends Plugin {
@Override
public boolean isCompatible() {
return device.getDeviceType().equals(Device.DeviceType.Tv) && super.isCompatible();
return device.getDeviceType().equals(DeviceType.Tv) && super.isCompatible();
}
@Override

View File

@@ -204,10 +204,12 @@ internal object AlbumArtCache {
}
//Only fetch an URL if we're not fetching it already
if (url in fetchUrlList || url in isFetchingList) {
return
synchronized(fetchUrlList) {
if (url in fetchUrlList || url in isFetchingList) {
return
}
fetchUrlList.add(url)
}
fetchUrlList.add(url)
initiateFetch()
}
@@ -215,12 +217,14 @@ internal object AlbumArtCache {
* Does the actual fetching and makes sure only not too many fetches are running at the same time
*/
private fun initiateFetch() {
if (numFetching >= 2 || fetchUrlList.isEmpty()) return
//Fetch the last-requested url first, it will probably be needed first
val url = fetchUrlList.last()
//Remove the url from the to-fetch list
fetchUrlList.remove(url)
var url : URL;
synchronized(fetchUrlList) {
if (numFetching >= 2 || fetchUrlList.isEmpty()) return
//Fetch the last-requested url first, it will probably be needed first
url = fetchUrlList.last()
//Remove the url from the to-fetch list
fetchUrlList.remove(url)
}
if ("file" == url.protocol) {
throw AssertionError("Not file urls should be possible here!")
}

View File

@@ -168,12 +168,13 @@ public class MprisMediaSession implements
* Prefers playing devices/mpris players, but tries to keep displaying the same
* player and device, while possible.
*/
private void updateCurrentPlayer() {
private MprisPlugin.MprisPlayer updateCurrentPlayer() {
Pair<Device, MprisPlugin.MprisPlayer> player = findPlayer();
//Update the last-displayed device and player
notificationDevice = player.first == null ? null : player.first.getDeviceId();
notificationPlayer = player.second;
return notificationPlayer;
}
private Pair<Device, MprisPlugin.MprisPlayer> findPlayer() {
@@ -263,6 +264,21 @@ public class MprisMediaSession implements
return;
}
Device device = KdeConnect.getInstance().getDevice(notificationDevice);
if (device == null) {
closeMediaNotification();
return;
}
//Make sure our information is up-to-date
MprisPlugin.MprisPlayer currentPlayer = updateCurrentPlayer();
//If the player disappeared (and no other playing one found), just remove the notification
if (currentPlayer == null) {
closeMediaNotification();
return;
}
synchronized (instance) {
if (mediaSession == null) {
mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG);
@@ -272,33 +288,24 @@ public class MprisMediaSession implements
}
}
//Make sure our information is up-to-date
updateCurrentPlayer();
//If the player disappeared (and no other playing one found), just remove the notification
if (notificationPlayer == null) {
closeMediaNotification();
return;
}
updateRemoteDeviceVolumeControl();
MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentPlayer.getTitle());
if (!notificationPlayer.getArtist().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, notificationPlayer.getArtist());
if (!currentPlayer.getArtist().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, currentPlayer.getArtist());
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentPlayer.getArtist());
}
if (!notificationPlayer.getAlbum().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
if (!currentPlayer.getAlbum().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentPlayer.getAlbum());
}
if (notificationPlayer.getLength() > 0) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
if (currentPlayer.getLength() > 0) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, currentPlayer.getLength());
}
Bitmap albumArt = notificationPlayer.getAlbumArt();
Bitmap albumArt = currentPlayer.getAlbumArt();
if (albumArt != null) {
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
}
@@ -306,17 +313,17 @@ public class MprisMediaSession implements
mediaSession.setMetadata(metadata.build());
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
if (notificationPlayer.isPlaying()) {
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
if (currentPlayer.isPlaying()) {
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, currentPlayer.getPosition(), 1.0f);
} else {
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f);
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, currentPlayer.getPosition(), 0.0f);
}
//Create all actions (previous/play/pause/next)
Intent iPlay = new Intent(context, MprisMediaNotificationReceiver.class);
iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY);
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.getPlayerName());
PendingIntent piPlay = PendingIntent.getBroadcast(context, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder(
R.drawable.ic_play_white, context.getString(R.string.mpris_play), piPlay);
@@ -324,7 +331,7 @@ public class MprisMediaSession implements
Intent iPause = new Intent(context, MprisMediaNotificationReceiver.class);
iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE);
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.getPlayerName());
PendingIntent piPause = PendingIntent.getBroadcast(context, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder(
R.drawable.ic_pause_white, context.getString(R.string.mpris_pause), piPause);
@@ -332,7 +339,7 @@ public class MprisMediaSession implements
Intent iPrevious = new Intent(context, MprisMediaNotificationReceiver.class);
iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS);
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.getPlayerName());
PendingIntent piPrevious = PendingIntent.getBroadcast(context, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder(
R.drawable.ic_previous_white, context.getString(R.string.mpris_previous), piPrevious);
@@ -340,14 +347,14 @@ public class MprisMediaSession implements
Intent iNext = new Intent(context, MprisMediaNotificationReceiver.class);
iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT);
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.getPlayerName());
PendingIntent piNext = PendingIntent.getBroadcast(context, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder(
R.drawable.ic_next_white, context.getString(R.string.mpris_next), piNext);
Intent iOpenActivity = new Intent(context, MprisActivity.class);
iOpenActivity.putExtra("deviceId", notificationDevice);
iOpenActivity.putExtra("player", notificationPlayer.getPlayerName());
iOpenActivity.putExtra("player", currentPlayer.getPlayerName());
PendingIntent piOpenActivity = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(iOpenActivity)
@@ -362,30 +369,30 @@ public class MprisMediaSession implements
.setShowWhen(false)
.setColor(ContextCompat.getColor(context, R.color.primary))
.setVisibility(androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC)
.setSubText(KdeConnect.getInstance().getDevice(notificationDevice).getName());
.setSubText(device.getName());
notification.setContentTitle(notificationPlayer.getTitle());
notification.setContentTitle(currentPlayer.getTitle());
//Only set the notification body text if we have an author and/or album
if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) {
notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")");
} else if (!notificationPlayer.getArtist().isEmpty()) {
notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayerName() + ")");
} else if (!notificationPlayer.getAlbum().isEmpty()) {
notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")");
if (!currentPlayer.getArtist().isEmpty() && !currentPlayer.getAlbum().isEmpty()) {
notification.setContentText(currentPlayer.getArtist() + " - " + currentPlayer.getAlbum() + " (" + currentPlayer.getPlayerName() + ")");
} else if (!currentPlayer.getArtist().isEmpty()) {
notification.setContentText(currentPlayer.getArtist() + " (" + currentPlayer.getPlayerName() + ")");
} else if (!currentPlayer.getAlbum().isEmpty()) {
notification.setContentText(currentPlayer.getAlbum() + " (" + currentPlayer.getPlayerName() + ")");
} else {
notification.setContentText(notificationPlayer.getPlayerName());
notification.setContentText(currentPlayer.getPlayerName());
}
if (albumArt != null) {
notification.setLargeIcon(albumArt);
}
if (!notificationPlayer.isPlaying()) {
if (!currentPlayer.isPlaying()) {
Intent iCloseNotification = new Intent(context, MprisMediaNotificationReceiver.class);
iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.getPlayerName());
PendingIntent piCloseNotification = PendingIntent.getBroadcast(context, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
notification.setDeleteIntent(piCloseNotification);
}
@@ -393,37 +400,37 @@ public class MprisMediaSession implements
//Add media control actions
int numActions = 0;
long playbackActions = 0;
if (notificationPlayer.isGoPreviousAllowed()) {
if (currentPlayer.isGoPreviousAllowed()) {
notification.addAction(aPrevious.build());
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
++numActions;
}
if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
if (currentPlayer.isPlaying() && currentPlayer.isPauseAllowed()) {
notification.addAction(aPause.build());
playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
++numActions;
}
if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
if (!currentPlayer.isPlaying() && currentPlayer.isPlayAllowed()) {
notification.addAction(aPlay.build());
playbackActions |= PlaybackStateCompat.ACTION_PLAY;
++numActions;
}
if (notificationPlayer.isGoNextAllowed()) {
if (currentPlayer.isGoNextAllowed()) {
notification.addAction(aNext.build());
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
++numActions;
}
// Documentation says that this was added in Lollipop (21) but it seems to cause crashes on < Pie (28)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (notificationPlayer.isSeekAllowed()) {
if (currentPlayer.isSeekAllowed()) {
playbackActions |= PlaybackStateCompat.ACTION_SEEK_TO;
}
}
playbackState.setActions(playbackActions);
mediaSession.setPlaybackState(playbackState.build());
//Only allow deletion if no music is notificationPlayer
notification.setOngoing(notificationPlayer.isPlaying());
//Only allow deletion if no music is currentPlayer
notification.setOngoing(currentPlayer.isPlaying());
//Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
MediaStyle mediaStyle = new MediaStyle();

View File

@@ -245,7 +245,10 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
return; //Player hasn't actually changed
}
targetPlayer = plugin.getPlayerStatus(player);
targetPlayerName = targetPlayer.getPlayerName();
if (targetPlayer != null) {
targetPlayerName = targetPlayer.getPlayerName();
}
updatePlayerStatus(plugin);
if (targetPlayer != null && targetPlayer.isPlaying()) {

View File

@@ -24,6 +24,10 @@ class MprisReceiverPlayer {
this.name = name;
}
public MediaController getController() {
return controller;
}
boolean isPlaying() {
PlaybackState state = controller.getPlaybackState();
if (state == null) return false;

View File

@@ -33,6 +33,8 @@ import org.kde.kdeconnect_tp.R;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@PluginFactory.LoadablePlugin
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@@ -43,6 +45,7 @@ public class MprisReceiverPlugin extends Plugin {
private static final String TAG = "MprisReceiver";
private HashMap<String, MprisReceiverPlayer> players;
private HashMap<String, MprisReceiverCallback> playerCbs;
private MediaSessionChangeListener mediaSessionChangeListener;
@Override
@@ -52,6 +55,7 @@ public class MprisReceiverPlugin extends Plugin {
return false;
players = new HashMap<>();
playerCbs = new HashMap<>();
try {
MediaSessionManager manager = ContextCompat.getSystemService(context, MediaSessionManager.class);
if (null == manager)
@@ -175,7 +179,10 @@ public class MprisReceiverPlugin extends Plugin {
if (null == controllers) {
return;
}
for (MprisReceiverPlayer p : players.values()) {
p.getController().unregisterCallback(playerCbs.get(p.getName()));
}
playerCbs.clear();
players.clear();
createPlayers(controllers);
@@ -189,7 +196,9 @@ public class MprisReceiverPlugin extends Plugin {
if (controller.getPackageName().equals(context.getPackageName())) return;
MprisReceiverPlayer player = new MprisReceiverPlayer(controller, AppsHelper.appNameLookup(context, controller.getPackageName()));
controller.registerCallback(new MprisReceiverCallback(this, player), new Handler(Looper.getMainLooper()));
MprisReceiverCallback cb = new MprisReceiverCallback(this, player);
controller.registerCallback(cb, new Handler(Looper.getMainLooper()));
playerCbs.put(player.getName(), cb);
players.put(player.getName(), player);
}
@@ -209,6 +218,9 @@ public class MprisReceiverPlugin extends Plugin {
np.set("player", player.getName());
np.set("title", player.getTitle());
np.set("artist", player.getArtist());
String nowPlaying = Stream.of(player.getArtist(), player.getTitle())
.filter(StringUtils::isNotEmpty).collect(Collectors.joining(" - "));
np.set("nowPlaying", nowPlaying); // GSConnect 50 (so, Ubuntu 22.04) needs this
np.set("album", player.getAlbum());
np.set("isPlaying", player.isPlaying());
np.set("pos", player.getPosition());

View File

@@ -168,7 +168,7 @@ public class PluginFactory {
//Check incoming against outgoing
if (Collections.disjoint(outgoing, info.getSupportedPacketTypes())
&& Collections.disjoint(incoming, info.getOutgoingPacketTypes())) {
Log.i("PluginFactory", "Won't load " + pluginId + " because of unmatched capabilities");
Log.d("PluginFactory", "Won't load " + pluginId + " because of unmatched capabilities");
continue; //No capabilities in common, do not load this plugin
}
plugins.add(pluginId);

View File

@@ -18,7 +18,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.DeviceType;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
@@ -41,7 +41,7 @@ public class PresenterPlugin extends Plugin {
@Override
public boolean isCompatible() {
return !device.getDeviceType().equals(Device.DeviceType.Phone) && super.isCompatible();
return !device.getDeviceType().equals(DeviceType.Phone) && super.isCompatible();
}
@Override

View File

@@ -92,7 +92,7 @@ class SimpleSftpServer {
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));
keyAuth.deviceKey = device.certificate.getPublicKey();
keyAuth.deviceKey = device.getCertificate().getPublicKey();
sshd.setPublickeyAuthenticator(keyAuth);
sshd.setPasswordAuthenticator(passwordAuth);

View File

@@ -1,117 +0,0 @@
/*
* SPDX-FileCopyrightText: 2014 The Android Open Source Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.kde.kdeconnect.UserInterface;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
* <p>
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@NonNull
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@@ -34,6 +34,7 @@ import org.kde.kdeconnect.Device
import org.kde.kdeconnect.Device.PluginsChangedListener
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
import org.kde.kdeconnect.KdeConnect
import org.kde.kdeconnect.PairingHandler
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin
import org.kde.kdeconnect.Plugins.Plugin

View File

@@ -9,7 +9,6 @@ package org.kde.kdeconnect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,7 +32,6 @@ import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
@@ -41,10 +39,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
@RunWith(PowerMockRunner.class)
@@ -89,7 +84,7 @@ public class DeviceTest {
MockSharedPreference deviceSettings = new MockSharedPreference();
SharedPreferences.Editor editor = deviceSettings.edit();
editor.putString("deviceName", name);
editor.putString("deviceType", Device.DeviceType.Phone.toString());
editor.putString("deviceType", DeviceType.Phone.toString());
editor.putString("certificate", encodedCertificate);
editor.apply();
Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
@@ -115,12 +110,11 @@ public class DeviceTest {
@Test
public void testDeviceType() {
assertEquals(Device.DeviceType.Phone, Device.DeviceType.FromString(Device.DeviceType.Phone.toString()));
assertEquals(Device.DeviceType.Tablet, Device.DeviceType.FromString(Device.DeviceType.Tablet.toString()));
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(Device.DeviceType.Computer.toString()));
assertEquals(Device.DeviceType.Tv, Device.DeviceType.FromString(Device.DeviceType.Tv.toString()));
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(""));
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(null));
assertEquals(DeviceType.Phone, DeviceType.fromString(DeviceType.Phone.toString()));
assertEquals(DeviceType.Tablet, DeviceType.fromString(DeviceType.Tablet.toString()));
assertEquals(DeviceType.Computer, DeviceType.fromString(DeviceType.Computer.toString()));
assertEquals(DeviceType.Tv, DeviceType.fromString(DeviceType.Tv.toString()));
assertEquals(DeviceType.Computer, DeviceType.fromString("invalid"));
}
// Basic paired device testing
@@ -129,10 +123,10 @@ public class DeviceTest {
Device device = new Device(context, "testDevice");
assertEquals(device.getDeviceId(), "testDevice");
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
assertEquals(device.getDeviceType(), DeviceType.Phone);
assertEquals(device.getName(), "Test Device");
assertTrue(device.isPaired());
assertNotNull(device.certificate);
assertNotNull(device.deviceInfo.certificate);
}
public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException {
@@ -142,7 +136,7 @@ public class DeviceTest {
fakeNetworkPacket.set("deviceId", deviceId);
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
fakeNetworkPacket.set("deviceType", DeviceType.Phone.toString());
String certificateString =
"MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
@@ -162,18 +156,21 @@ public class DeviceTest {
"7n+KOQ==";
byte[] certificateBytes = Base64.decode(certificateString, 0);
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(fakeNetworkPacket, certificate);
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
LanLink link = Mockito.mock(LanLink.class);
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
Device device = new Device(context, deviceId, certificate, fakeNetworkPacket, link);
Mockito.when(link.getDeviceId()).thenReturn(deviceId);
Mockito.when(link.getDeviceInfo()).thenReturn(deviceInfo);
Device device = new Device(context, link);
assertNotNull(device);
assertEquals(device.getDeviceId(), deviceId);
assertEquals(device.getName(), "Unpaired Test Device");
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
assertNotNull(device.certificate);
assertEquals(device.getDeviceType(), DeviceType.Phone);
assertNotNull(device.deviceInfo.certificate);
Method method = PairingHandler.class.getDeclaredMethod("pairingDone");
method.setAccessible(true);

View File

@@ -10,10 +10,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import android.content.Context;
import android.util.Log;
import org.json.JSONException;
@@ -22,10 +19,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.security.cert.Certificate;
@RunWith(PowerMockRunner.class)
@PrepareForTest({DeviceHelper.class, Log.class})
public class NetworkPacketTest {
@@ -34,7 +34,7 @@ public class NetworkPacketTest {
public void setUp() {
PowerMockito.mockStatic(DeviceHelper.class);
PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123");
PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(Device.DeviceType.Phone);
PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(DeviceType.Phone);
PowerMockito.mockStatic(Log.class);
}
@@ -70,14 +70,23 @@ public class NetworkPacketTest {
@Test
public void testIdentity() {
Certificate cert = Mockito.mock(Certificate.class);
Context context = Mockito.mock(Context.class);
MockSharedPreference settings = new MockSharedPreference();
Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(settings);
DeviceInfo deviceInfo = new DeviceInfo("myid", cert, "myname", DeviceType.Tv, 12, Sets.newSet("ASDFG"), Sets.newSet("QWERTY"));
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
NetworkPacket np = deviceInfo.toIdentityPacket();
assertEquals(np.getInt("protocolVersion"), 12);
DeviceInfo parsed = DeviceInfo.fromIdentityPacketAndCert(np, cert);
assertEquals(parsed.name, deviceInfo.name);
assertEquals(parsed.id, deviceInfo.id);
assertEquals(parsed.type, deviceInfo.type);
assertEquals(parsed.protocolVersion, deviceInfo.protocolVersion);
assertEquals(parsed.incomingCapabilities, deviceInfo.incomingCapabilities);
assertEquals(parsed.outgoingCapabilities, deviceInfo.outgoingCapabilities);
assertEquals(np.getInt("protocolVersion"), DeviceHelper.ProtocolVersion);
}