mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-31 22:25:08 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
268bc833be | ||
|
d62a7fbcdc | ||
|
8c9fc6586b | ||
|
0d658e6fb6 | ||
|
020382931c | ||
|
cc0b94bd3d | ||
|
5c0c190f5a |
@@ -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"
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -1 +1 @@
|
||||
KDE Connect intègre votre téléphone et votre ordinateur.
|
||||
KDE Connect integrates your smartphone and computer
|
@@ -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 ""
|
||||
|
@@ -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"
|
@@ -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>
|
||||
|
@@ -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"><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,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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
206
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal file
206
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@@ -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!")
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user