2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-31 22:25:08 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Albert Vaca Cintora
268bc833be Merge branch 'work/deviceinfo' into work/all 2023-06-20 10:53:49 +02:00
Albert Vaca Cintora
d62a7fbcdc Remove redundant toString 2023-06-20 10:52:57 +02:00
Albert Vaca Cintora
8c9fc6586b Add IP as a txt record for iOS compat 2023-06-20 10:50:55 +02:00
Albert Vaca Cintora
0d658e6fb6 Add a setting to enable/disable UDP broadcast 2023-06-20 10:42:49 +02:00
Albert Vaca Cintora
020382931c Add MDNS discovery 2023-06-20 10:33:14 +02:00
Albert Vaca Cintora
cc0b94bd3d Prevent reloading plugins twice in a row 2023-06-20 10:27:03 +02:00
Albert Vaca Cintora
5c0c190f5a Add DeviceInfo class
It 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, the BaseLink subclasses need to implement 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.
2023-06-20 10:27:03 +02:00
19 changed files with 299 additions and 147 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="12602"
android:versionName="1.26.2">
android:versionCode="12600"
android:versionName="1.26.0">
<uses-feature
android:name="android.hardware.telephony"

View File

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

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

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

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

View File

@@ -1,19 +1,17 @@
# 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-27 08:54+0200\n"
"Last-Translator: Xavier BESNARD <xavier.besnard@neuf.fr>\n"
"Language-Team: fr\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"
"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 "KDE Connect intègre votre téléphone et votre ordinateur."
msgstr ""

View File

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

@@ -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">Baterie: %d%% nabíjí se</string>
<string name="battery_status_charging_format">Battery: %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 volání a SMS z těchto čísel. Prosím, zadejte pouze jedno slovo na řádek.</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="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

@@ -60,8 +60,6 @@
<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>
@@ -376,7 +374,6 @@
<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,5 +396,4 @@
<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

@@ -108,7 +108,6 @@
<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>
@@ -396,5 +395,4 @@
<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

@@ -547,4 +547,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="plugin_stats">Plugin stats</string>
<string name="enable_udp_broadcast">Enable backwards-compatible device discovery</string>
</resources>

View File

@@ -25,6 +25,7 @@ import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
import org.kde.kdeconnect.KdeConnect;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
import org.kde.kdeconnect.UserInterface.SettingsFragment;
import java.io.BufferedReader;
import java.io.IOException;
@@ -66,10 +67,12 @@ public class LanLinkProvider extends BaseLinkProvider {
private final Context context;
private final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
private ServerSocket tcpServer;
private DatagramSocket udpServer;
ServerSocket tcpServer;
DatagramSocket udpServer;
MdnsDiscovery mdnsDiscovery;
private long lastBroadcast = 0;
private final static long delayBetweenBroadcasts = 200;
@@ -129,10 +132,6 @@ 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);
@@ -231,13 +230,9 @@ public class LanLinkProvider extends BaseLinkProvider {
* @param deviceInfo remote device info
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket)}
*/
private void addLink(SSLSocket socket, DeviceInfo deviceInfo) throws IOException {
private LanLink 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);
@@ -248,10 +243,12 @@ public class LanLinkProvider extends BaseLinkProvider {
visibleDevices.put(deviceInfo.id, link);
onConnectionReceived(link);
}
return link;
}
public LanLinkProvider(Context context) {
this.context = context;
this.mdnsDiscovery = new MdnsDiscovery(context, this);
}
private void setupUdpListener() {
@@ -339,6 +336,12 @@ public class LanLinkProvider extends BaseLinkProvider {
}
private void broadcastUdpIdentityPacket() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (!preferences.getBoolean(SettingsFragment.KEY_UDP_BROADCAST_ENABLED, true)) {
Log.i("LanLinkProvider", "UDP broadcast is disabled in settings. Skipping.");
return;
}
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
Log.i("LanLinkProvider", "broadcastUdpPacket: relax cowboy");
return;
@@ -423,6 +426,9 @@ public class LanLinkProvider extends BaseLinkProvider {
setupUdpListener();
setupTcpListener();
mdnsDiscovery.startListening();
mdnsDiscovery.startAnnouncing();
broadcastUdpIdentityPacket();
}
}
@@ -430,6 +436,8 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override
public void onNetworkChange() {
broadcastUdpIdentityPacket();
mdnsDiscovery.stopListening();
mdnsDiscovery.startListening();
}
@Override
@@ -446,6 +454,8 @@ public class LanLinkProvider extends BaseLinkProvider {
} catch (Exception e) {
Log.e("LanLink", "Exception", e);
}
mdnsDiscovery.stopAnnouncing();
mdnsDiscovery.stopListening();
}
@Override

View File

@@ -0,0 +1,206 @@
/*
* 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.Backends.LanBackend;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import java.net.InetAddress;
import java.util.Collections;
public class MdnsDiscovery {
static final String LOG_TAG = "MdnsDiscovery";
static final String SERVICE_TYPE = "_kdeconnect._udp";
private final Context context;
private final LanLinkProvider lanLinkProvider;
private final NsdManager mNsdManager;
private NsdManager.RegistrationListener registrationListener;
private NsdManager.DiscoveryListener discoveryListener;
public MdnsDiscovery(Context context, LanLinkProvider lanLinkProvider) {
this.context = context;
this.lanLinkProvider = lanLinkProvider;
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
}
void startListening() {
if (discoveryListener == null) {
discoveryListener = createDiscoveryListener();
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
}
}
void stopListening() {
if (discoveryListener != null) {
mNsdManager.stopServiceDiscovery(discoveryListener);
discoveryListener = null;
}
}
void stopAnnouncing() {
if (registrationListener != null) {
mNsdManager.unregisterService(registrationListener);
registrationListener = null;
}
}
void startAnnouncing() {
if (registrationListener == null) {
registrationListener = createRegistrationListener();
NsdServiceInfo serviceInfo = createNsdServiceInfo();
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
}
}
NsdManager.RegistrationListener createRegistrationListener() {
return new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
// If Android changed the service name to avoid conflicts, here we can read it.
Log.i(LOG_TAG, "Registered " + serviceInfo.getServiceName());
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(LOG_TAG, "Registration failed with: " + errorCode);
}
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
Log.d(LOG_TAG, "Service unregistered: " + serviceInfo);
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode);
}
};
}
public NsdServiceInfo createNsdServiceInfo() {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
InetAddress address = lanLinkProvider.udpServer.getInetAddress();
int port = lanLinkProvider.udpServer.getLocalPort();
serviceInfo.setHost(address);
serviceInfo.setPort(port);
// iOS seems to need these as a TXT records
serviceInfo.setAttribute("ip", address.toString());
serviceInfo.setAttribute("port", Integer.toString(port));
// The following fields aren't really used for anything, since we can't include enough info
// for it to be useful (namely: we can't include the device certificate).
// Each field (key + value) needs to be < 255 bytes. All the fields combined need to be < 1300 bytes.
// Also, on Android Lollipop those fields aren't resolved.
String deviceId = DeviceHelper.getDeviceId(context);
String deviceName = DeviceHelper.getDeviceName(context);
String deviceType = DeviceHelper.getDeviceType(context).toString();
String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion);
serviceInfo.setAttribute("id", deviceId);
serviceInfo.setAttribute("name", deviceName);
serviceInfo.setAttribute("type", deviceType);
serviceInfo.setAttribute("version", protocolVersion);
// Without resolving the DNS, the service name is the only info we have so it must be sufficient to identify a device.
// Also, it must be unique, otherwise it will be automatically renamed. For these reasons we use the deviceId.
serviceInfo.setServiceName(deviceId);
serviceInfo.setServiceType(SERVICE_TYPE);
Log.d(LOG_TAG, "My MDNS info: " + serviceInfo);
return serviceInfo;
}
NsdManager.DiscoveryListener createDiscoveryListener() {
return new NsdManager.DiscoveryListener() {
final String myId = DeviceHelper.getDeviceId(context);
@Override
public void onDiscoveryStarted(String regType) {
Log.i(LOG_TAG, "Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
Log.d(LOG_TAG, "Service discovered: " + serviceInfo);
String deviceId = serviceInfo.getServiceName();
if (myId.equals(deviceId)) {
Log.d(LOG_TAG, "Discovered myself, ignoring.");
return;
}
if (lanLinkProvider.visibleDevices.containsKey(deviceId)) {
Log.i(LOG_TAG, "MDNS discovered " + deviceId + " to which I'm already connected to. Ignoring.");
return;
}
mNsdManager.resolveService(serviceInfo, createResolveListener());
}
@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
Log.w(LOG_TAG, "Service lost: " + serviceInfo);
// We can't see this device via mdns. This probably means it's not reachable anymore
// but we do nothing here since we have other ways to do detect unreachable devices
// that hopefully will also trigger.
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(LOG_TAG, "MDNS discovery stopped: " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "MDNS discovery start failed: " + errorCode);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "MDNS discovery stop failed: " + errorCode);
}
};
}
/**
* Returns a new listener instance since NsdManager wants a different listener each time you call resolveService
*/
NsdManager.ResolveListener createResolveListener() {
return new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.w(LOG_TAG, "MDNS error " + errorCode + " resolving service: " + serviceInfo);
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.i(LOG_TAG, "MDNS successfully resolved " + serviceInfo);
InetAddress remoteAddress = serviceInfo.getHost();
// Let the LanLinkProvider handle the connection
lanLinkProvider.sendUdpIdentityPacket(Collections.singletonList(remoteAddress));
}
};
}
}

View File

@@ -204,12 +204,10 @@ internal object AlbumArtCache {
}
//Only fetch an URL if we're not fetching it already
synchronized(fetchUrlList) {
if (url in fetchUrlList || url in isFetchingList) {
return
}
fetchUrlList.add(url)
if (url in fetchUrlList || url in isFetchingList) {
return
}
fetchUrlList.add(url)
initiateFetch()
}
@@ -217,14 +215,12 @@ internal object AlbumArtCache {
* Does the actual fetching and makes sure only not too many fetches are running at the same time
*/
private fun initiateFetch() {
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 (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)
if ("file" == url.protocol) {
throw AssertionError("Not file urls should be possible here!")
}

View File

@@ -168,13 +168,12 @@ public class MprisMediaSession implements
* Prefers playing devices/mpris players, but tries to keep displaying the same
* player and device, while possible.
*/
private MprisPlugin.MprisPlayer updateCurrentPlayer() {
private void 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() {
@@ -274,10 +273,10 @@ public class MprisMediaSession implements
}
//Make sure our information is up-to-date
MprisPlugin.MprisPlayer currentPlayer = updateCurrentPlayer();
updateCurrentPlayer();
//If the player disappeared (and no other playing one found), just remove the notification
if (currentPlayer == null) {
if (notificationPlayer == null) {
closeMediaNotification();
return;
}
@@ -286,20 +285,20 @@ public class MprisMediaSession implements
MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentPlayer.getTitle());
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
if (!currentPlayer.getArtist().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, currentPlayer.getArtist());
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentPlayer.getArtist());
if (!notificationPlayer.getArtist().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, notificationPlayer.getArtist());
}
if (!currentPlayer.getAlbum().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentPlayer.getAlbum());
if (!notificationPlayer.getAlbum().isEmpty()) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
}
if (currentPlayer.getLength() > 0) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, currentPlayer.getLength());
if (notificationPlayer.getLength() > 0) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
}
Bitmap albumArt = currentPlayer.getAlbumArt();
Bitmap albumArt = notificationPlayer.getAlbumArt();
if (albumArt != null) {
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
}
@@ -307,17 +306,17 @@ public class MprisMediaSession implements
mediaSession.setMetadata(metadata.build());
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
if (currentPlayer.isPlaying()) {
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, currentPlayer.getPosition(), 1.0f);
if (notificationPlayer.isPlaying()) {
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
} else {
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, currentPlayer.getPosition(), 0.0f);
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.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, currentPlayer.getPlayerName());
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.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);
@@ -325,7 +324,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, currentPlayer.getPlayerName());
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.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);
@@ -333,7 +332,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, currentPlayer.getPlayerName());
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.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);
@@ -341,14 +340,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, currentPlayer.getPlayerName());
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.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", currentPlayer.getPlayerName());
iOpenActivity.putExtra("player", notificationPlayer.getPlayerName());
PendingIntent piOpenActivity = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(iOpenActivity)
@@ -365,28 +364,28 @@ public class MprisMediaSession implements
.setVisibility(androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC)
.setSubText(KdeConnect.getInstance().getDevice(notificationDevice).getName());
notification.setContentTitle(currentPlayer.getTitle());
notification.setContentTitle(notificationPlayer.getTitle());
//Only set the notification body text if we have an author and/or album
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() + ")");
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() + ")");
} else {
notification.setContentText(currentPlayer.getPlayerName());
notification.setContentText(notificationPlayer.getPlayerName());
}
if (albumArt != null) {
notification.setLargeIcon(albumArt);
}
if (!currentPlayer.isPlaying()) {
if (!notificationPlayer.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, currentPlayer.getPlayerName());
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName());
PendingIntent piCloseNotification = PendingIntent.getBroadcast(context, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
notification.setDeleteIntent(piCloseNotification);
}
@@ -394,37 +393,37 @@ public class MprisMediaSession implements
//Add media control actions
int numActions = 0;
long playbackActions = 0;
if (currentPlayer.isGoPreviousAllowed()) {
if (notificationPlayer.isGoPreviousAllowed()) {
notification.addAction(aPrevious.build());
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
++numActions;
}
if (currentPlayer.isPlaying() && currentPlayer.isPauseAllowed()) {
if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
notification.addAction(aPause.build());
playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
++numActions;
}
if (!currentPlayer.isPlaying() && currentPlayer.isPlayAllowed()) {
if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
notification.addAction(aPlay.build());
playbackActions |= PlaybackStateCompat.ACTION_PLAY;
++numActions;
}
if (currentPlayer.isGoNextAllowed()) {
if (notificationPlayer.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 (currentPlayer.isSeekAllowed()) {
if (notificationPlayer.isSeekAllowed()) {
playbackActions |= PlaybackStateCompat.ACTION_SEEK_TO;
}
}
playbackState.setActions(playbackActions);
mediaSession.setPlaybackState(playbackState.build());
//Only allow deletion if no music is currentPlayer
notification.setOngoing(currentPlayer.isPlaying());
//Only allow deletion if no music is notificationPlayer
notification.setOngoing(notificationPlayer.isPlaying());
//Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
MediaStyle mediaStyle = new MediaStyle();

View File

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

View File

@@ -33,8 +33,6 @@ 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)
@@ -45,7 +43,6 @@ 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
@@ -55,7 +52,6 @@ public class MprisReceiverPlugin extends Plugin {
return false;
players = new HashMap<>();
playerCbs = new HashMap<>();
try {
MediaSessionManager manager = ContextCompat.getSystemService(context, MediaSessionManager.class);
if (null == manager)
@@ -179,10 +175,7 @@ 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);
@@ -196,9 +189,7 @@ public class MprisReceiverPlugin extends Plugin {
if (controller.getPackageName().equals(context.getPackageName())) return;
MprisReceiverPlayer player = new MprisReceiverPlayer(controller, AppsHelper.appNameLookup(context, controller.getPackageName()));
MprisReceiverCallback cb = new MprisReceiverCallback(this, player);
controller.registerCallback(cb, new Handler(Looper.getMainLooper()));
playerCbs.put(player.getName(), cb);
controller.registerCallback(new MprisReceiverCallback(this, player), new Handler(Looper.getMainLooper()));
players.put(player.getName(), player);
}
@@ -218,9 +209,6 @@ 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

@@ -37,6 +37,9 @@ import org.kde.kdeconnect_tp.R;
public class SettingsFragment extends PreferenceFragmentCompat {
public static final String KEY_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
public static final String KEY_APP_THEME = "theme_pref";
private EditTextPreference renameDevice;
@NonNull
@@ -90,7 +93,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
// Theme Selector
ListPreference themeSelector = new ListPreference(context);
themeSelector.setKey("theme_pref");
themeSelector.setKey(KEY_APP_THEME);
themeSelector.setTitle(R.string.theme_dialog_title);
themeSelector.setDialogTitle(R.string.theme_dialog_title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -168,6 +171,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true;
});
// UDP broadcast toggle
final TwoStatePreference udpBroadcastDiscovery = new SwitchPreference(context);
udpBroadcastDiscovery.setPersistent(false);
udpBroadcastDiscovery.setDefaultValue(true);
udpBroadcastDiscovery.setKey(KEY_UDP_BROADCAST_ENABLED);
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
screen.addPreference(udpBroadcastDiscovery);
// More settings text
Preference moreSettingsText = new Preference(context);