mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Working bluetooth implementation
This commit is contained in:
parent
22b6427a18
commit
19ec81eec4
@ -17,8 +17,10 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" /> -->
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> -->
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
@ -55,4 +55,5 @@ public abstract class BaseLinkProvider {
|
|||||||
public abstract void onNetworkChange(@Nullable Network network);
|
public abstract void onNetworkChange(@Nullable Network network);
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
|
public abstract int getPriority();
|
||||||
}
|
}
|
||||||
|
@ -189,10 +189,4 @@ public class BluetoothLink extends BaseLink {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
public boolean isConnected() {
|
|
||||||
return socket.isConnected();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* SPDX-FileCopyrightText: 2016 Saikrishna Arcot <saiarcot895@gmail.com>
|
* SPDX-FileCopyrightText: 2016 Saikrishna Arcot <saiarcot895@gmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
||||||
|
|
||||||
@ -14,8 +14,11 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@ -29,6 +32,7 @@ import org.kde.kdeconnect.Helpers.DeviceHelper;
|
|||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
import org.kde.kdeconnect.UserInterface.SettingsFragment;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -39,6 +43,7 @@ import java.security.cert.Certificate;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -58,6 +63,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
private ServerRunnable serverRunnable;
|
private ServerRunnable serverRunnable;
|
||||||
private ClientRunnable clientRunnable;
|
private ClientRunnable clientRunnable;
|
||||||
|
|
||||||
|
private Random randomiser;
|
||||||
|
|
||||||
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
|
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
|
||||||
String deviceId = identityPacket.getString("deviceId");
|
String deviceId = identityPacket.getString("deviceId");
|
||||||
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
||||||
@ -66,7 +73,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!");
|
Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
synchronized (visibleDevices) {
|
||||||
visibleDevices.put(deviceId, link);
|
visibleDevices.put(deviceId, link);
|
||||||
|
}
|
||||||
onConnectionReceived(link);
|
onConnectionReceived(link);
|
||||||
link.startListening();
|
link.startListening();
|
||||||
link.packetReceived(identityPacket);
|
link.packetReceived(identityPacket);
|
||||||
@ -79,6 +88,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
public BluetoothLinkProvider(Context context) {
|
public BluetoothLinkProvider(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
|
this.randomiser = new Random();
|
||||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
if (bluetoothAdapter == null) {
|
if (bluetoothAdapter == null) {
|
||||||
Log.e("BluetoothLinkProvider", "No bluetooth adapter found.");
|
Log.e("BluetoothLinkProvider", "No bluetooth adapter found.");
|
||||||
@ -87,6 +97,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (bluetoothAdapter == null) {
|
if (bluetoothAdapter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,6 +114,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("BluetoothLinkProvider", "onStart called");
|
||||||
|
|
||||||
//This handles the case when I'm the existing device in the network and receive a hello package
|
//This handles the case when I'm the existing device in the network and receive a hello package
|
||||||
clientRunnable = new ClientRunnable();
|
clientRunnable = new ClientRunnable();
|
||||||
ThreadHelper.execute(clientRunnable);
|
ThreadHelper.execute(clientRunnable);
|
||||||
@ -111,6 +127,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNetworkChange(@Nullable Network network) {
|
public void onNetworkChange(@Nullable Network network) {
|
||||||
|
Log.i("BluetoothLinkProvider", "onNetworkChange called");
|
||||||
|
|
||||||
onStop();
|
onStop();
|
||||||
onStart();
|
onStart();
|
||||||
}
|
}
|
||||||
@ -120,7 +138,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
|
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.i("BluetoothLinkProvider", "onStop called");
|
||||||
clientRunnable.stopProcessing();
|
clientRunnable.stopProcessing();
|
||||||
serverRunnable.stopProcessing();
|
serverRunnable.stopProcessing();
|
||||||
}
|
}
|
||||||
@ -130,9 +148,20 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
return "BluetoothLinkProvider";
|
return "BluetoothLinkProvider";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) {
|
public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) {
|
||||||
|
|
||||||
|
Log.i("BluetoothLinkProvider", "disconnectedLink called");
|
||||||
|
synchronized (sockets) {
|
||||||
sockets.remove(remoteAddress);
|
sockets.remove(remoteAddress);
|
||||||
|
}
|
||||||
|
synchronized (visibleDevices) {
|
||||||
visibleDevices.remove(link.getDeviceId());
|
visibleDevices.remove(link.getDeviceId());
|
||||||
|
}
|
||||||
onConnectionLost(link);
|
onConnectionLost(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,15 +187,18 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("KDEConnect", "Exception", e);
|
Log.e("KDEConnect", "Exception", e);
|
||||||
return;
|
return;
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e("KDEConnect", "Security Exception for CONNECT", e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (continueProcessing) {
|
|
||||||
try {
|
try {
|
||||||
|
while (continueProcessing) {
|
||||||
BluetoothSocket socket = serverSocket.accept();
|
BluetoothSocket socket = serverSocket.accept();
|
||||||
connect(socket);
|
connect(socket);
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("BTLinkProvider/Server", "Bluetooth error", e);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d("BTLinkProvider/Server", "Bluetooth Server error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +231,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||||
NetworkPacket np = myDeviceInfo.toIdentityPacket();
|
NetworkPacket np = myDeviceInfo.toIdentityPacket();
|
||||||
|
|
||||||
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
||||||
|
|
||||||
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
||||||
@ -226,29 +259,38 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
Log.i("BTLinkProvider/Server", "Received identity packet");
|
Log.i("BTLinkProvider/Server", "Received identity packet");
|
||||||
|
|
||||||
String certificateString = identityPacket.getString("certificate");
|
String PemEncodedCertificateString = identityPacket.getString("certificate");
|
||||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
String base64CertificateString = PemEncodedCertificateString
|
||||||
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
.replace("-----BEGIN CERTIFICATE-----\n","")
|
||||||
|
.replace("-----END CERTIFICATE-----\n","");
|
||||||
|
byte[] PEMEncodedCertificateBytes = Base64.decode(base64CertificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(PEMEncodedCertificateBytes);
|
||||||
|
|
||||||
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
||||||
|
Log.i("BTLinkProvider/Server", "About to create link");
|
||||||
BluetoothLink link = new BluetoothLink(context, connection,
|
BluetoothLink link = new BluetoothLink(context, connection,
|
||||||
inputStream, outputStream, socket.getRemoteDevice(),
|
inputStream, outputStream, socket.getRemoteDevice(),
|
||||||
deviceInfo, BluetoothLinkProvider.this);
|
deviceInfo, BluetoothLinkProvider.this);
|
||||||
|
Log.i("BTLinkProvider/Server", "About to addLink");
|
||||||
addLink(identityPacket, link);
|
addLink(identityPacket, link);
|
||||||
|
Log.i("BTLinkProvider/Server", "Link Added");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
synchronized (sockets) {
|
synchronized (sockets) {
|
||||||
sockets.remove(socket.getRemoteDevice());
|
sockets.remove(socket.getRemoteDevice());
|
||||||
|
Log.i("BTLinkProvider/Server", "Exception thrown, removing socket", e);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ClientRunnableSingleton {
|
||||||
|
|
||||||
|
private static Map<BluetoothDevice, Thread> connectionThreads = new HashMap<>();
|
||||||
|
}
|
||||||
private class ClientRunnable extends BroadcastReceiver implements Runnable {
|
private class ClientRunnable extends BroadcastReceiver implements Runnable {
|
||||||
|
|
||||||
private boolean continueProcessing = true;
|
private boolean continueProcessing = true;
|
||||||
private final Map<BluetoothDevice, Thread> connectionThreads = new HashMap<>();
|
|
||||||
|
|
||||||
void stopProcessing() {
|
void stopProcessing() {
|
||||||
continueProcessing = false;
|
continueProcessing = false;
|
||||||
@ -256,31 +298,57 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Log.i("ClientRunnable", "run called");
|
||||||
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
|
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
|
||||||
context.registerReceiver(this, filter);
|
context.registerReceiver(this, filter);
|
||||||
|
Log.i("ClientRunnable", "receiver registered");
|
||||||
if (continueProcessing) {
|
if (continueProcessing) {
|
||||||
connectToDevices();
|
Log.i("ClientRunnable", "before connectToDevices");
|
||||||
|
discoverDeviceServices();
|
||||||
|
Log.i("ClientRunnable", "after connectToDevices");
|
||||||
try {
|
try {
|
||||||
Thread.sleep(15000);
|
Thread.sleep(15000);
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("ClientRunnable", "unregisteringReceiver");
|
||||||
context.unregisterReceiver(this);
|
context.unregisterReceiver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectToDevices() {
|
/**
|
||||||
|
* Tell Android to use ServiceDiscoveryProtocol to update the
|
||||||
|
* list of available UUIDs associated with Bluetooth devices
|
||||||
|
* that are bluetooth-paired-but-not-yet-kde-paired
|
||||||
|
*/
|
||||||
|
private void discoverDeviceServices() {
|
||||||
|
|
||||||
|
Log.i("ClientRunnable", "connectToDevices called");
|
||||||
|
|
||||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||||
|
if(pairedDevices == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size());
|
Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size());
|
||||||
|
|
||||||
// Loop through paired devices
|
// Loop through Bluetooth paired devices
|
||||||
for (BluetoothDevice device : pairedDevices) {
|
for (BluetoothDevice device : pairedDevices) {
|
||||||
|
// If a socket exists for this, then it has been paired in KDE
|
||||||
if (sockets.containsKey(device)) {
|
if (sockets.containsKey(device)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("ClientRunnable", "Calling fetchUuidsWithSdp for device: "+device);
|
||||||
|
|
||||||
device.fetchUuidsWithSdp();
|
device.fetchUuidsWithSdp();
|
||||||
|
ParcelUuid[] deviceUuids = device.getUuids();
|
||||||
|
|
||||||
|
if(deviceUuids != null){
|
||||||
|
for(ParcelUuid thisUuid : deviceUuids) {
|
||||||
|
Log.i("ClientRunnable", "device "+device + " uuid: "+ thisUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,14 +377,14 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void connectToDevice(BluetoothDevice device) {
|
private void connectToDevice(BluetoothDevice device) {
|
||||||
if (!connectionThreads.containsKey(device) || !connectionThreads.get(device).isAlive()) {
|
synchronized (ClientRunnableSingleton.connectionThreads) {
|
||||||
|
if (!ClientRunnableSingleton.connectionThreads.containsKey(device) || !ClientRunnableSingleton.connectionThreads.get(device).isAlive()) {
|
||||||
Thread connectionThread = new Thread(new ClientConnect(device));
|
Thread connectionThread = new Thread(new ClientConnect(device));
|
||||||
connectionThread.start();
|
connectionThread.start();
|
||||||
connectionThreads.put(device, connectionThread);
|
ClientRunnableSingleton.connectionThreads.put(device, connectionThread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClientConnect implements Runnable {
|
private class ClientConnect implements Runnable {
|
||||||
@ -335,12 +403,21 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
private void connectToDevice() {
|
private void connectToDevice() {
|
||||||
BluetoothSocket socket;
|
BluetoothSocket socket;
|
||||||
try {
|
try {
|
||||||
|
Log.i("BTLinkProvider/Client", "Cancelling Discovery");
|
||||||
|
bluetoothAdapter.cancelDiscovery();
|
||||||
|
Log.i("BTLinkProvider/Client", "Creating RFCommSocket to Service Record");
|
||||||
socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID);
|
socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID);
|
||||||
|
Log.i("BTLinkProvider/Client", "Connecting to ServiceRecord Socket");
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
synchronized (sockets) {
|
||||||
sockets.put(device, socket);
|
sockets.put(device, socket);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e);
|
Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e);
|
||||||
return;
|
return;
|
||||||
|
} catch (SecurityException e){
|
||||||
|
Log.e("BTLinkProvider/Client", "Security Exception connecting to " + device.getAddress(), e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress());
|
Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress());
|
||||||
@ -348,19 +425,25 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
try {
|
try {
|
||||||
//Delay to let bluetooth initialize stuff correctly
|
//Delay to let bluetooth initialize stuff correctly
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
||||||
ConnectionMultiplexer connection = new ConnectionMultiplexer(socket);
|
ConnectionMultiplexer connection = new ConnectionMultiplexer(socket);
|
||||||
OutputStream outputStream = connection.getDefaultOutputStream();
|
OutputStream outputStream = connection.getDefaultOutputStream();
|
||||||
InputStream inputStream = connection.getDefaultInputStream();
|
InputStream inputStream = connection.getDefaultInputStream();
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before inputStream.read()");
|
||||||
int character;
|
int character;
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
while (sb.lastIndexOf("\n") == -1 && (character = inputStream.read()) != -1) {
|
while (sb.lastIndexOf("\n") == -1 && (character = inputStream.read()) != -1) {
|
||||||
sb.append((char) character);
|
sb.append((char) character);
|
||||||
}
|
}
|
||||||
|
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before sb.toString()");
|
||||||
|
|
||||||
String message = sb.toString();
|
String message = sb.toString();
|
||||||
|
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before unserialize (message: '"+message+"')");
|
||||||
final NetworkPacket identityPacket = NetworkPacket.unserialize(message);
|
final NetworkPacket identityPacket = NetworkPacket.unserialize(message);
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" After unserialize");
|
||||||
|
|
||||||
if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
|
if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
|
||||||
Log.e("BTLinkProvider/Client", "1 Expecting an identity packet");
|
Log.e("BTLinkProvider/Client", "1 Expecting an identity packet");
|
||||||
socket.close();
|
socket.close();
|
||||||
@ -382,9 +465,12 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
Log.i("BTLinkProvider/Client", "identity packet received, creating link");
|
Log.i("BTLinkProvider/Client", "identity packet received, creating link");
|
||||||
|
|
||||||
String certificateString = identityPacket.getString("certificate");
|
String PemEncodedCertificateString = identityPacket.getString("certificate");
|
||||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
String base64CertificateString = PemEncodedCertificateString
|
||||||
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
.replace("-----BEGIN CERTIFICATE-----\n","")
|
||||||
|
.replace("-----END CERTIFICATE-----\n","");
|
||||||
|
byte[] PEMEncodedCertificateBytes = Base64.decode(base64CertificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(PEMEncodedCertificateBytes);
|
||||||
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
||||||
|
|
||||||
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
|
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
|
||||||
@ -394,6 +480,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
NetworkPacket np2 = myDeviceInfo.toIdentityPacket();
|
NetworkPacket np2 = myDeviceInfo.toIdentityPacket();
|
||||||
np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "about to send packet np2");
|
||||||
|
|
||||||
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
@ -409,8 +497,12 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e);
|
Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e);
|
||||||
|
synchronized (sockets) {
|
||||||
|
sockets.remove(device, socket);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
|
|
||||||
@ -245,7 +246,7 @@ public final class ConnectionMultiplexer implements Closeable {
|
|||||||
channel.doClose();
|
channel.doClose();
|
||||||
}
|
}
|
||||||
channels.clear();
|
channels.clear();
|
||||||
if (socket.isConnected()) {
|
if (socket != null && socket.isConnected()) {
|
||||||
try {
|
try {
|
||||||
socket.close();
|
socket.close();
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
@ -438,6 +439,16 @@ public final class ConnectionMultiplexer implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String byteArrayToHexString(final byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for(byte b : bytes){
|
||||||
|
sb.append(String.format("0x%02x ", b&0xff));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void read_message() throws IOException {
|
private void read_message() throws IOException {
|
||||||
byte[] data = new byte[BUFFER_SIZE];
|
byte[] data = new byte[BUFFER_SIZE];
|
||||||
read_buffer(data, 19);
|
read_buffer(data, 19);
|
||||||
@ -451,6 +462,10 @@ public final class ConnectionMultiplexer implements Closeable {
|
|||||||
UUID channel_id = new UUID(channel_id_msb, channel_id_lsb);
|
UUID channel_id = new UUID(channel_id_msb, channel_id_lsb);
|
||||||
|
|
||||||
if (!receivedProtocolVersion && type != MESSAGE_PROTOCOL_VERSION) {
|
if (!receivedProtocolVersion && type != MESSAGE_PROTOCOL_VERSION) {
|
||||||
|
Log.w("ConnectionMultiplexer", "Received invalid message '" + message+"'");
|
||||||
|
Log.w("ConnectionMultiplexer", "'data_buffer:("+byteArrayToHexString(data)+") ");
|
||||||
|
Log.w("ConnectionMultiplexer", "as string: '"+(new String(data, "UTF-8"))+"' ");
|
||||||
|
|
||||||
throw new IOException("Did not receive protocol version message!");
|
throw new IOException("Did not receive protocol version message!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,11 +553,15 @@ public final class ConnectionMultiplexer implements Closeable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
while (true) {
|
while (true) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (!open) return;
|
if (!open) {
|
||||||
|
Log.w("ConnectionMultiplexer", "connection not open, returning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
read_message();
|
read_message();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Log.w("ConnectionMultiplexer", "run caught IOException", e);
|
||||||
handleException(e);
|
handleException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -494,4 +494,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
return "LanLinkProvider";
|
return "LanLinkProvider";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() { return 20; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,4 +40,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return "LoopbackLinkProvider";
|
return "LoopbackLinkProvider";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() { return 0; }
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
|
import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider;
|
||||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity;
|
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity;
|
||||||
@ -82,7 +83,7 @@ public class BackgroundService extends Service {
|
|||||||
private void registerLinkProviders() {
|
private void registerLinkProviders() {
|
||||||
linkProviders.add(new LanLinkProvider(this));
|
linkProviders.add(new LanLinkProvider(this));
|
||||||
// linkProviders.add(new LoopbackLinkProvider(this));
|
// linkProviders.add(new LoopbackLinkProvider(this));
|
||||||
// linkProviders.add(new BluetoothLinkProvider(this));
|
linkProviders.add(new BluetoothLinkProvider(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onNetworkChange(@Nullable Network network) {
|
public void onNetworkChange(@Nullable Network network) {
|
||||||
|
@ -37,7 +37,10 @@ import org.kde.kdeconnect_tp.R;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -61,6 +64,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
||||||
private final SharedPreferences settings;
|
private final SharedPreferences settings;
|
||||||
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
||||||
|
private String connectivityType;
|
||||||
|
|
||||||
public boolean supportsPacketType(String type) {
|
public boolean supportsPacketType(String type) {
|
||||||
if (deviceInfo.incomingCapabilities == null) {
|
if (deviceInfo.incomingCapabilities == null) {
|
||||||
@ -70,6 +74,10 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getConnectivityType() {
|
||||||
|
return connectivityType;
|
||||||
|
}
|
||||||
|
|
||||||
public interface PluginsChangedListener {
|
public interface PluginsChangedListener {
|
||||||
void onPluginsChanged(@NonNull Device device);
|
void onPluginsChanged(@NonNull Device device);
|
||||||
}
|
}
|
||||||
@ -84,6 +92,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings);
|
this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings);
|
||||||
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
|
||||||
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
||||||
|
this.connectivityType = "";
|
||||||
Log.i("Device","Loading trusted device: " + deviceInfo.name);
|
Log.i("Device","Loading trusted device: " + deviceInfo.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +107,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
|
this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
|
||||||
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
|
||||||
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
||||||
|
this.connectivityType = link.getLinkProvider().getName();
|
||||||
Log.i("Device","Creating untrusted device: "+ deviceInfo.name);
|
Log.i("Device","Creating untrusted device: "+ deviceInfo.name);
|
||||||
addLink(link);
|
addLink(link);
|
||||||
}
|
}
|
||||||
@ -302,7 +312,14 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
packetQueue = new DevicePacketQueue(this);
|
packetQueue = new DevicePacketQueue(this);
|
||||||
}
|
}
|
||||||
//FilesHelper.LogOpenFileCount();
|
//FilesHelper.LogOpenFileCount();
|
||||||
|
|
||||||
links.add(link);
|
links.add(link);
|
||||||
|
|
||||||
|
List linksToSort = Arrays.asList(links.toArray());
|
||||||
|
Collections.sort(linksToSort, (Comparator<BaseLink>) (o1, o2) -> Integer.compare(o2.getLinkProvider().getPriority(), o1.getLinkProvider().getPriority()));
|
||||||
|
links.clear();
|
||||||
|
links.addAll(linksToSort);
|
||||||
|
|
||||||
link.addPacketReceiver(this);
|
link.addPacketReceiver(this);
|
||||||
|
|
||||||
boolean hasChanges = updateDeviceInfo(link.getDeviceInfo());
|
boolean hasChanges = updateDeviceInfo(link.getDeviceInfo());
|
||||||
|
@ -6,13 +6,17 @@
|
|||||||
|
|
||||||
package org.kde.kdeconnect;
|
package org.kde.kdeconnect;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
@ -23,6 +27,7 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
|||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
|
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||||
import org.kde.kdeconnect_tp.BuildConfig;
|
import org.kde.kdeconnect_tp.BuildConfig;
|
||||||
import org.slf4j.impl.HandroidLoggerAdapter;
|
import org.slf4j.impl.HandroidLoggerAdapter;
|
||||||
@ -62,6 +67,7 @@ public class KdeConnect extends Application {
|
|||||||
NotificationHelper.initializeChannels(this);
|
NotificationHelper.initializeChannels(this);
|
||||||
LifecycleHelper.initializeObserver();
|
LifecycleHelper.initializeObserver();
|
||||||
loadRememberedDevicesFromSettings();
|
loadRememberedDevicesFromSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSL4JLogging() {
|
private void setupSL4JLogging() {
|
||||||
|
@ -39,6 +39,7 @@ public class PairingDeviceItem implements ListAdapter.Item {
|
|||||||
final ListItemWithIconEntryBinding binding = ListItemWithIconEntryBinding.inflate(layoutInflater);
|
final ListItemWithIconEntryBinding binding = ListItemWithIconEntryBinding.inflate(layoutInflater);
|
||||||
|
|
||||||
binding.listItemEntryIcon.setImageDrawable(device.getIcon());
|
binding.listItemEntryIcon.setImageDrawable(device.getIcon());
|
||||||
|
// binding.listItemEntryTitle.setText(device.getName() + " " + device.getConnectivityType());
|
||||||
binding.listItemEntryTitle.setText(device.getName());
|
binding.listItemEntryTitle.setText(device.getName());
|
||||||
|
|
||||||
if (device.compareProtocolVersion() != 0) {
|
if (device.compareProtocolVersion() != 0) {
|
||||||
|
@ -39,6 +39,7 @@ import org.kde.kdeconnect.UserInterface.About.AboutFragment
|
|||||||
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
|
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
|
||||||
import org.kde.kdeconnect_tp.R
|
import org.kde.kdeconnect_tp.R
|
||||||
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
|
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
|
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
|
||||||
private const val MENU_ENTRY_SETTINGS = 2
|
private const val MENU_ENTRY_SETTINGS = 2
|
||||||
@ -195,14 +196,20 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val missingPermissions = mutableListOf<String>()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
val permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
val permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
|
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
|
||||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
|
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
|
||||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), RESULT_NOTIFICATIONS_ENABLED)
|
missingPermissions.add(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(missingPermissions.size > 0){
|
||||||
|
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), RESULT_NOTIFICATIONS_ENABLED)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
package org.kde.kdeconnect.UserInterface;
|
package org.kde.kdeconnect.UserInterface;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@ -20,6 +22,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.preference.EditTextPreference;
|
import androidx.preference.EditTextPreference;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
@ -38,6 +41,7 @@ import org.kde.kdeconnect_tp.R;
|
|||||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
public static final String KEY_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
|
public static final String KEY_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
|
||||||
|
public static final String KEY_BLUETOOTH_ENABLED = "bluetooth_enabled";
|
||||||
public static final String KEY_APP_THEME = "theme_pref";
|
public static final String KEY_APP_THEME = "theme_pref";
|
||||||
|
|
||||||
private EditTextPreference renameDevice;
|
private EditTextPreference renameDevice;
|
||||||
@ -178,6 +182,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
|
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
|
||||||
screen.addPreference(udpBroadcastDiscovery);
|
screen.addPreference(udpBroadcastDiscovery);
|
||||||
|
|
||||||
|
final TwoStatePreference enableBluetoothSupport = new SwitchPreference(context);
|
||||||
|
enableBluetoothSupport.setDefaultValue(false);
|
||||||
|
enableBluetoothSupport.setKey(KEY_BLUETOOTH_ENABLED);
|
||||||
|
enableBluetoothSupport.setTitle("Enable bluetooth (beta)");
|
||||||
|
enableBluetoothSupport.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && (boolean)newValue) {
|
||||||
|
ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN}, 2);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
screen.addPreference(enableBluetoothSupport);
|
||||||
|
|
||||||
// More settings text
|
// More settings text
|
||||||
Preference moreSettingsText = new Preference(context);
|
Preference moreSettingsText = new Preference(context);
|
||||||
moreSettingsText.setPersistent(false);
|
moreSettingsText.setPersistent(false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user