mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-01 14:45:08 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e53338c70e | ||
|
e641ff5a0a | ||
|
2ebaf6ae5b | ||
|
dd89463d75 | ||
|
7194b308cb | ||
|
328b708083 | ||
|
6404b86373 | ||
|
3263b37c8a | ||
|
a31476951a | ||
|
1d105bbb3d | ||
|
3154eef6a2 | ||
|
97a0389d04 | ||
|
8c1603f6e4 | ||
|
310e61b570 | ||
|
bfa4d05e0d | ||
|
6568bb486c | ||
|
a46fa23419 | ||
|
a29aeaad92 | ||
|
79744dc17b |
@@ -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="12601"
|
||||
android:versionName="1.26.1">
|
||||
android:versionCode="12603"
|
||||
android:versionName="1.26.3">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
|
12
fastlane/metadata/android/en-US/changelogs/12602.txt
Normal file
12
fastlane/metadata/android/en-US/changelogs/12602.txt
Normal 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.
|
12
fastlane/metadata/android/en-US/changelogs/12603.txt
Normal file
12
fastlane/metadata/android/en-US/changelogs/12603.txt
Normal 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.
|
@@ -1 +1 @@
|
||||
KDE Connect integrates your smartphone and computer
|
||||
KDE Connect intègre votre téléphone et votre ordinateur.
|
86
po/bg/kdeconnect-android-store.po
Normal file
86
po/bg/kdeconnect-android-store.po
Normal file
@@ -0,0 +1,86 @@
|
||||
# KDE Connect store listing texts
|
||||
# Copyright (C) 2014 Albert Vaca Cintora
|
||||
# This file is distributed under the same license as kdeconnect-android.
|
||||
#
|
||||
# Albert Vaca Cintora <albertvaka@gmail.com>, 2014.
|
||||
# Mincho Kondarev <mkondarev@yahoo.de>, 2023.
|
||||
msgid ""
|
||||
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-22 20:53+0200\n"
|
||||
"Last-Translator: Mincho Kondarev <mkondarev@yahoo.de>\n"
|
||||
"Language: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"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.2\n"
|
||||
|
||||
#: dummy:1
|
||||
msgid "Integrate Android with the KDE Plasma Desktop."
|
||||
msgstr "Интеграция на Android с работната среда Plasma на KDE."
|
||||
|
||||
#: dummy:2
|
||||
msgid ""
|
||||
"KDE Connect provides several features to integrate your phone and your"
|
||||
" computer:\n"
|
||||
"\n"
|
||||
"- Share files and URLs to KDE from any app, without wires.\n"
|
||||
"- Touchpad emulation: Use your phone screen as your computer's touchpad*.\n"
|
||||
"- Notifications sync (4.3+): Read your Android notifications from the"
|
||||
" desktop.\n"
|
||||
"- Shared clipboard: copy and paste between your phone and your computer.\n"
|
||||
"- Multimedia remote control: Use your phone as a remote for Linux media"
|
||||
" players.\n"
|
||||
"- WiFi connection: no usb wire or bluetooth needed.\n"
|
||||
"- RSA 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"
|
||||
"*NOTE for Ubuntu users: The Ubuntu folks are not updating their repos as fast"
|
||||
" as this app gets updated. Some features will not work if the KDE Connect"
|
||||
" version in you desktop doesn't match the one in your phone. To make sure you"
|
||||
" always have the latest version on your desktop, use this PPA repository: "
|
||||
" https://code.launchpad.net/~vikoadi/+archive/ubuntu/ppa/ \n"
|
||||
"\n"
|
||||
"This app is part of an open source project, visit the website to grab the"
|
||||
" sources.\n"
|
||||
msgstr ""
|
||||
"KDE Connect предоставя няколко функции за интегриране на телефона с "
|
||||
"компютъра:\n"
|
||||
"\n"
|
||||
"- Безжично поделяне на файлове и URL адреси в KDE от всяко приложение.\n"
|
||||
"- Емулация на тъчпад: Използвайте екрана на телефона си като тъчпад на"
|
||||
" компютъра*.\n"
|
||||
"- Синхронизиране на известия (4.3+): Четете известията си за Android от "
|
||||
"десктопа.\n"
|
||||
"- Споделен клипборд: копиране и поставяне между телефона и компютъра.\n"
|
||||
"- Мултимедийно дистанционно управление: Използвайте телефона си като"
|
||||
" дистанционно управление за Linux мултимедийни "
|
||||
"плейъри.\n"
|
||||
"- WiFi връзка: не е необходим USB кабел или Bluetooth.\n"
|
||||
"- RSA криптиране: информацията ви е сигурна.\n"
|
||||
"\n"
|
||||
"Моля, обърнете внимание, че за тази функция ще трябва да инсталирате KDE"
|
||||
" Connect на компютъра си "
|
||||
"и да поддържате версията за настолни компютри в актуално състояние с версията"
|
||||
" за Android, "
|
||||
"за да работят най-новите функции.\n"
|
||||
"\n"
|
||||
"*Забележка за потребителите на Ubuntu: Ubuntu не актуализират своите"
|
||||
" хранилища "
|
||||
"толкова бързо, колкото се обновява това приложение. Някои функции няма да"
|
||||
" работят, ако KDE "
|
||||
"Connect в десктопа не съвпада с версията в телефона ви. За да да сте сигурни,"
|
||||
" че винаги "
|
||||
"имате най-новата версия на десктопа си, използвайте това PPA "
|
||||
"хранилище: https://code.launchpad.net/~vikoadi/+archive/ubuntu/ppa/ \n"
|
||||
"\n"
|
||||
"Това приложение е част от проект с отворен код, посетете уебсайта, за да"
|
||||
" изтеглите "
|
||||
"изходния код.\n"
|
@@ -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."
|
||||
|
19
po/pt_BR/kdeconnect-android-store-short.po
Normal file
19
po/pt_BR/kdeconnect-android-store-short.po
Normal 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"
|
@@ -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"><h1>Tietoa</h1> <p>KDE on ohjelmoijien, taiteilijoiden, kirjoittajien, kääntäjien ja muiden sisällönluojien kansainvälinen yhteisö, joka on sitoutunut <a href=https://www.gnu.org/philosophy/free-sw.html>vapaiden ohjelmien</a> kehitykseen. KDE tuottaa Plasma-työpöytäympäristöä, satoja sovelluksia ja monia niitä tukevia ohjelmakirjastoja.</p> <p>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 <a href=https://community.kde.org/Get_Involved>liittymään ja avustamaan</a> KDE:ta – myös sinä.</p> Sivulta <a href=https://www.kde.org/>https://www.kde.org/</a> löytyy KDE-yhteisöstä ja tuottamistamme ohjelmista lisätietoa.</string>
|
||||
<string name="about_kde_report_bugs_or_wishes"><h1>Ilmoita ohjelmavirheistä tai -toiveista</h1> <p>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.</p> <p>KDE:lla on virheenseurantajärjestelmä. Käy sivulla <a href=https://bugs.kde.org/>https://bugs.kde.org/</a> tai käytä Tietoa-sivun painiketta ”Ilmoita ohjelmavirheestä”.</p> Parannusehdotuksissakin olet tervetullut käyttämään virheenseurantajärjestelmää kirjataksesi toiveesi. Varmista, että käytät vakavuustasoa ”Wishlist”.</string>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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,7 +55,7 @@ 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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
@@ -36,13 +30,10 @@ public abstract class BaseLinkProvider {
|
||||
/**
|
||||
* To be called from the child classes when a link to a new device is established
|
||||
*/
|
||||
protected void onConnectionReceived(@NonNull final String deviceId,
|
||||
@NonNull final Certificate certificate,
|
||||
@NonNull final NetworkPacket identityPacket,
|
||||
@NonNull final BaseLink link) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
@@ -54,10 +56,6 @@ 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 = visibleDevices.get(deviceId);
|
||||
if (oldLink == link) {
|
||||
@@ -65,8 +63,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
||||
return;
|
||||
}
|
||||
visibleDevices.put(deviceId, link);
|
||||
onConnectionReceived(deviceId, certificate, identityPacket, link);
|
||||
onConnectionReceived(link);
|
||||
link.startListening();
|
||||
link.packetReceived(identityPacket);
|
||||
if (oldLink != null) {
|
||||
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
|
||||
oldLink.disconnect();
|
||||
@@ -127,9 +126,9 @@ 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);
|
||||
visibleDevices.remove(deviceId);
|
||||
visibleDevices.remove(link.getDeviceId());
|
||||
onConnectionLost(link);
|
||||
}
|
||||
|
||||
@@ -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,7 +367,7 @@ 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();
|
||||
@@ -373,10 +380,18 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
||||
|
||||
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() {
|
||||
|
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -42,6 +43,8 @@ public class LanLink extends BaseLink {
|
||||
Locally, Remotely
|
||||
}
|
||||
|
||||
private final DeviceInfo deviceInfo;
|
||||
|
||||
private volatile SSLSocket socket = null;
|
||||
|
||||
@Override
|
||||
@@ -99,8 +102,9 @@ public class LanLink extends BaseLink {
|
||||
return oldSocket;
|
||||
}
|
||||
|
||||
public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, SSLSocket socket) throws IOException {
|
||||
super(context, deviceId, 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);
|
||||
}
|
||||
|
||||
@@ -109,6 +113,11 @@ public class LanLink extends BaseLink {
|
||||
return "LanLink";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@Override
|
||||
public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) {
|
||||
|
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -50,7 +51,7 @@ import kotlin.text.Charsets;
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
@@ -128,13 +129,19 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
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();
|
||||
NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context);
|
||||
out.write(myIdentity.serialize().getBytes());
|
||||
out.flush();
|
||||
|
||||
@@ -190,17 +197,19 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
||||
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];
|
||||
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);
|
||||
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;
|
||||
@@ -218,24 +227,26 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
/**
|
||||
* 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 = visibleDevices.get(deviceId);
|
||||
if (currentLink != null) {
|
||||
//Update old link
|
||||
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
|
||||
final Socket oldSocket = currentLink.reset(socket);
|
||||
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);
|
||||
visibleDevices.put(deviceId, link);
|
||||
onConnectionReceived(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +379,8 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkPacket identity = NetworkPacket.createIdentityPacket(context);
|
||||
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||
NetworkPacket identity = myDeviceInfo.toIdentityPacket();
|
||||
identity.set("tcpPort", tcpServer.getLocalPort());
|
||||
|
||||
byte[] bytes;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
onConnectionReceived(deviceId, SslHelper.certificate, np, new LoopbackLink(context, this));
|
||||
LoopbackLink link = new LoopbackLink(context, this);
|
||||
onConnectionReceived(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -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;
|
||||
@@ -38,13 +36,8 @@ 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;
|
||||
@@ -53,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,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() {
|
||||
@@ -195,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,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) {
|
||||
@@ -334,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()))
|
||||
@@ -365,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);
|
||||
}
|
||||
@@ -373,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) {
|
||||
@@ -417,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) {
|
||||
|
||||
@@ -580,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;
|
||||
@@ -641,8 +574,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
return false;
|
||||
}
|
||||
|
||||
plugins.put(pluginKey, plugin);
|
||||
|
||||
if (!plugin.checkRequiredPermissions()) {
|
||||
Log.d("KDE/addPlugin", "No permission " + pluginKey);
|
||||
plugins.remove(pluginKey);
|
||||
@@ -650,6 +581,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
return false;
|
||||
} else {
|
||||
Log.d("KDE/addPlugin", "Permissions OK " + pluginKey);
|
||||
plugins.put(pluginKey, plugin);
|
||||
pluginsWithoutPermissions.remove(pluginKey);
|
||||
if (plugin.checkOptionalPermissions()) {
|
||||
Log.d("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
|
||||
@@ -697,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) {
|
||||
|
133
src/org/kde/kdeconnect/DeviceInfo.kt
Normal file
133
src/org/kde/kdeconnect/DeviceInfo.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
@@ -24,8 +24,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
|
@@ -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;
|
||||
@@ -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() {
|
||||
|
@@ -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
|
||||
|
@@ -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!")
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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()) {
|
||||
|
@@ -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;
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -84,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);
|
||||
@@ -110,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
|
||||
@@ -124,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 {
|
||||
@@ -137,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" +
|
||||
@@ -157,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);
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user