mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 18:07:55 +00:00
Add Bluetooth support
This is a ported version of the patch submited in review: https://git.reviewboard.kde.org/r/128270 It is disabled by default. REVIEW: 128270
This commit is contained in:
parent
5d7b0a976a
commit
20455f74ab
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
<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_ADMIN" />-->
|
||||||
<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_WIFI_MULTICAST_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Saikrishna Arcot <saiarcot895@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
|
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||||
|
import org.kde.kdeconnect.NetworkPackage;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||||
|
public class BluetoothLink extends BaseLink {
|
||||||
|
private final BluetoothSocket socket;
|
||||||
|
private final BluetoothLinkProvider linkProvider;
|
||||||
|
|
||||||
|
private boolean continueAccepting = true;
|
||||||
|
|
||||||
|
private Thread receivingThread = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try {
|
||||||
|
Reader reader = new InputStreamReader(socket.getInputStream(), "UTF-8");
|
||||||
|
char[] buf = new char[512];
|
||||||
|
while (continueAccepting) {
|
||||||
|
while (sb.indexOf("\n") == -1 && continueAccepting) {
|
||||||
|
int charsRead;
|
||||||
|
if ((charsRead = reader.read(buf)) > 0) {
|
||||||
|
sb.append(buf, 0, charsRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = sb.indexOf("\n");
|
||||||
|
if (endIndex != -1) {
|
||||||
|
String message = sb.substring(0, endIndex + 1);
|
||||||
|
sb.delete(0, endIndex + 1);
|
||||||
|
processMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("BluetoothLink/receiving", "Connection to " + socket.getRemoteDevice().getAddress() + " likely broken.", e);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMessage(String message) {
|
||||||
|
NetworkPackage np;
|
||||||
|
try {
|
||||||
|
np = NetworkPackage.unserialize(message);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e("BluetoothLink/receiving", "Unable to parse message.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
|
||||||
|
try {
|
||||||
|
np = RsaHelper.decrypt(np, privateKey);
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.e("BluetoothLink/receiving", "Exception decrypting the package", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (np.hasPayloadTransferInfo()) {
|
||||||
|
BluetoothSocket transferSocket = null;
|
||||||
|
try {
|
||||||
|
UUID transferUuid = UUID.fromString(np.getPayloadTransferInfo().getString("uuid"));
|
||||||
|
transferSocket = socket.getRemoteDevice().createRfcommSocketToServiceRecord(transferUuid);
|
||||||
|
transferSocket.connect();
|
||||||
|
np.setPayload(transferSocket.getInputStream(), np.getPayloadSize());
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (transferSocket != null) {
|
||||||
|
try { transferSocket.close(); } catch(IOException ignored) { }
|
||||||
|
}
|
||||||
|
Log.e("BluetoothLink/receiving", "Unable to get payload", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packageReceived(np);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public BluetoothLink(Context context, BluetoothSocket socket, String deviceId, BluetoothLinkProvider linkProvider) {
|
||||||
|
super(context, deviceId, linkProvider);
|
||||||
|
this.socket = socket;
|
||||||
|
this.linkProvider = linkProvider;
|
||||||
|
receivingThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "BluetoothLink";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
|
||||||
|
return new BluetoothPairingHandler(device, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
if (socket == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
continueAccepting = false;
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
linkProvider.disconnectedLink(this, getDeviceId(), socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(NetworkPackage np) throws JSONException, IOException {
|
||||||
|
byte[] message = np.serialize().getBytes(Charset.forName("UTF-8"));
|
||||||
|
OutputStream socket = this.socket.getOutputStream();
|
||||||
|
Log.i("BluetoothLink","Beginning to send message");
|
||||||
|
socket.write(message);
|
||||||
|
Log.i("BluetoothLink","Finished sending message");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sendPackage(NetworkPackage np, Device.SendPackageStatusCallback callback) {
|
||||||
|
return sendPackageInternal(np, callback, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||||
|
return sendPackageInternal(np, callback, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendPackageInternal(NetworkPackage np, final Device.SendPackageStatusCallback callback, PublicKey key) {
|
||||||
|
|
||||||
|
/*if (!isConnected()) {
|
||||||
|
Log.e("BluetoothLink", "sendPackageEncrypted failed: not connected");
|
||||||
|
callback.sendFailure(new Exception("Not connected"));
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
BluetoothServerSocket serverSocket = null;
|
||||||
|
if (np.hasPayload()) {
|
||||||
|
UUID transferUuid = UUID.randomUUID();
|
||||||
|
serverSocket = BluetoothAdapter.getDefaultAdapter()
|
||||||
|
.listenUsingRfcommWithServiceRecord("KDE Connect Transfer", transferUuid);
|
||||||
|
JSONObject payloadTransferInfo = new JSONObject();
|
||||||
|
payloadTransferInfo.put("uuid", transferUuid.toString());
|
||||||
|
np.setPayloadTransferInfo(payloadTransferInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != null) {
|
||||||
|
try {
|
||||||
|
np = RsaHelper.encrypt(np, key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(np);
|
||||||
|
|
||||||
|
if (serverSocket != null) {
|
||||||
|
BluetoothSocket transferSocket = serverSocket.accept();
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
|
||||||
|
int idealBufferLength = 4096;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
idealBufferLength = transferSocket.getMaxReceivePacketSize();
|
||||||
|
}
|
||||||
|
byte[] buffer = new byte[idealBufferLength];
|
||||||
|
int bytesRead;
|
||||||
|
long progress = 0;
|
||||||
|
InputStream stream = np.getPayload();
|
||||||
|
while ((bytesRead = stream.read(buffer)) != -1) {
|
||||||
|
progress += bytesRead;
|
||||||
|
transferSocket.getOutputStream().write(buffer, 0, bytesRead);
|
||||||
|
if (np.getPayloadSize() > 0) {
|
||||||
|
callback.onProgressChanged((int) (100 * progress / np.getPayloadSize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transferSocket.getOutputStream().flush();
|
||||||
|
stream.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try { transferSocket.close(); } catch (IOException ignored) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onSuccess();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean linkShouldBeKeptAlive() {
|
||||||
|
return receivingThread.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public boolean isConnected() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||||
|
return socket.isConnected();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
@ -0,0 +1,377 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Saikrishna Arcot <saiarcot895@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.NetworkPackage;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||||
|
public class BluetoothLinkProvider extends BaseLinkProvider {
|
||||||
|
|
||||||
|
private static final UUID SERVICE_UUID = UUID.fromString("185f3df4-3268-4e3f-9fca-d4d5059915bd");
|
||||||
|
private static final int REQUEST_ENABLE_BT = 48;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Map<String, BluetoothLink> visibleComputers = new HashMap<>();
|
||||||
|
private final Map<BluetoothDevice, BluetoothSocket> sockets = new HashMap<>();
|
||||||
|
|
||||||
|
private BluetoothAdapter bluetoothAdapter = null;
|
||||||
|
|
||||||
|
private ServerRunnable serverRunnable;
|
||||||
|
private ClientRunnable clientRunnable;
|
||||||
|
|
||||||
|
private void addLink(NetworkPackage identityPackage, BluetoothLink link) {
|
||||||
|
String deviceId = identityPackage.getString("deviceId");
|
||||||
|
Log.i("BluetoothLinkProvider","addLink to "+deviceId);
|
||||||
|
BluetoothLink oldLink = visibleComputers.get(deviceId);
|
||||||
|
if (oldLink == link) {
|
||||||
|
Log.e("KDEConnect", "BluetoothLinkProvider: oldLink == link. This should not happen!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visibleComputers.put(deviceId, link);
|
||||||
|
connectionAccepted(identityPackage, link);
|
||||||
|
if (oldLink != null) {
|
||||||
|
Log.i("BluetoothLinkProvider","Removing old connection to same device");
|
||||||
|
oldLink.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLinkProvider(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
if (bluetoothAdapter == null) {
|
||||||
|
Log.e("BluetoothLinkProvider","No bluetooth adapter found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
if (bluetoothAdapter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bluetoothAdapter.isEnabled()) {
|
||||||
|
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||||
|
Log.e("BluetoothLinkProvider","Bluetooth adapter not enabled.");
|
||||||
|
// TODO: next line needs to be called from an existing activity, so move it?
|
||||||
|
// startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
|
||||||
|
// TODO: Check result of the previous command, whether the user allowed bluetooth or not.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//This handles the case when I'm the existing device in the network and receive a hello package
|
||||||
|
clientRunnable = new ClientRunnable();
|
||||||
|
new Thread(clientRunnable).start();
|
||||||
|
|
||||||
|
// I'm on a new network, let's be polite and introduce myself
|
||||||
|
serverRunnable = new ServerRunnable();
|
||||||
|
new Thread(serverRunnable).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNetworkChange() {
|
||||||
|
onStop();
|
||||||
|
onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientRunnable.stopProcessing();
|
||||||
|
serverRunnable.stopProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "BluetoothLinkProvider";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothSocket socket) {
|
||||||
|
sockets.remove(socket.getRemoteDevice());
|
||||||
|
visibleComputers.remove(deviceId);
|
||||||
|
connectionLost(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ServerRunnable implements Runnable {
|
||||||
|
|
||||||
|
private boolean continueProcessing = true;
|
||||||
|
private BluetoothServerSocket serverSocket;
|
||||||
|
|
||||||
|
void stopProcessing() {
|
||||||
|
continueProcessing = false;
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
serverSocket = bluetoothAdapter
|
||||||
|
.listenUsingRfcommWithServiceRecord("KDE Connect", SERVICE_UUID);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continueProcessing) {
|
||||||
|
try {
|
||||||
|
BluetoothSocket socket = serverSocket.accept();
|
||||||
|
connect(socket);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(BluetoothSocket socket) throws Exception {
|
||||||
|
//socket.connect();
|
||||||
|
OutputStream outputStream = socket.getOutputStream();
|
||||||
|
if (sockets.containsKey(socket.getRemoteDevice())) {
|
||||||
|
Log.i("BTLinkProvider/Server", "Received duplicate connection from " + socket.getRemoteDevice().getAddress());
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
sockets.put(socket.getRemoteDevice(), socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Server", "Received connection from " + socket.getRemoteDevice().getAddress());
|
||||||
|
|
||||||
|
NetworkPackage np = NetworkPackage.createIdentityPackage(context);
|
||||||
|
byte[] message = np.serialize().getBytes("UTF-8");
|
||||||
|
outputStream.write(message);
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Server", "Sent identity package");
|
||||||
|
|
||||||
|
// Listen for the response
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
Reader reader = new InputStreamReader(socket.getInputStream(), "UTF-8");
|
||||||
|
int charsRead;
|
||||||
|
char[] buf = new char[512];
|
||||||
|
while(sb.lastIndexOf("\n") == -1 && (charsRead = reader.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, charsRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
String response = sb.toString();
|
||||||
|
final NetworkPackage identityPackage = NetworkPackage.unserialize(response);
|
||||||
|
|
||||||
|
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||||
|
Log.e("BTLinkProvider/Server", "2 Expecting an identity package");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Server", "Received identity package");
|
||||||
|
|
||||||
|
BluetoothLink link = new BluetoothLink(context, socket,
|
||||||
|
identityPackage.getString("deviceId"), BluetoothLinkProvider.this);
|
||||||
|
|
||||||
|
addLink(identityPackage, link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClientRunnable extends BroadcastReceiver implements Runnable {
|
||||||
|
|
||||||
|
private boolean continueProcessing = true;
|
||||||
|
private Map<BluetoothDevice, Thread> connectionThreads = new HashMap<>();
|
||||||
|
|
||||||
|
void stopProcessing() {
|
||||||
|
continueProcessing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||||
|
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
|
||||||
|
context.registerReceiver(this, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (continueProcessing) {
|
||||||
|
connectToDevices();
|
||||||
|
try {
|
||||||
|
Thread.sleep(15000);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||||
|
context.unregisterReceiver(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectToDevices() {
|
||||||
|
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||||
|
Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size());
|
||||||
|
|
||||||
|
// Loop through paired devices
|
||||||
|
for (BluetoothDevice device : pairedDevices) {
|
||||||
|
if (sockets.containsKey(device)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||||
|
device.fetchUuidsWithSdp();
|
||||||
|
} else {
|
||||||
|
connectToDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(value=Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action.equals(BluetoothDevice.ACTION_UUID)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
Parcelable[] activeUuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
|
||||||
|
|
||||||
|
if (sockets.containsKey(device)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeUuids == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Parcelable uuid: activeUuids) {
|
||||||
|
if (uuid.toString().equals(SERVICE_UUID.toString())) {
|
||||||
|
connectToDevice(device);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectToDevice(BluetoothDevice device) {
|
||||||
|
if (!connectionThreads.containsKey(device) || !connectionThreads.get(device).isAlive()) {
|
||||||
|
Thread connectionThread = new Thread(new ClientConnect(device));
|
||||||
|
connectionThread.start();
|
||||||
|
connectionThreads.put(device, connectionThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClientConnect implements Runnable {
|
||||||
|
|
||||||
|
private final BluetoothDevice device;
|
||||||
|
|
||||||
|
public ClientConnect(BluetoothDevice device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
connectToDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectToDevice() {
|
||||||
|
BluetoothSocket socket;
|
||||||
|
try {
|
||||||
|
socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID);
|
||||||
|
socket.connect();
|
||||||
|
sockets.put(device, socket);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress());
|
||||||
|
|
||||||
|
try {
|
||||||
|
int character;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while(sb.lastIndexOf("\n") == -1 && (character = socket.getInputStream().read()) != -1) {
|
||||||
|
sb.append((char)character);
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = sb.toString();
|
||||||
|
final NetworkPackage identityPackage = NetworkPackage.unserialize(message);
|
||||||
|
|
||||||
|
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
|
||||||
|
Log.e("BTLinkProvider/Client", "1 Expecting an identity package");
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "Received identity package");
|
||||||
|
|
||||||
|
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
|
||||||
|
if (identityPackage.getString("deviceId").equals(myId)) {
|
||||||
|
// Probably won't happen, but just to be safe
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleComputers.containsKey(identityPackage.getString("deviceId"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("BTLinkProvider/Client", "Identity package received, creating link");
|
||||||
|
|
||||||
|
final BluetoothLink link = new BluetoothLink(context, socket,
|
||||||
|
identityPackage.getString("deviceId"), BluetoothLinkProvider.this);
|
||||||
|
|
||||||
|
NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
|
||||||
|
link.sendPackage(np2,new Device.SendPackageStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
addLink(identityPackage, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Backends.BluetoothBackend;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.NetworkPackage;
|
||||||
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class BluetoothPairingHandler extends BasePairingHandler {
|
||||||
|
|
||||||
|
Timer mPairingTimer;
|
||||||
|
public BluetoothPairingHandler(Device device, final PairingHandlerCallback callback) {
|
||||||
|
super(device, callback);
|
||||||
|
|
||||||
|
if (device.isPaired()) {
|
||||||
|
mPairStatus = PairStatus.Paired;
|
||||||
|
} else {
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
public NetworkPackage createPairPackage() {
|
||||||
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||||
|
np.set("pair", true);
|
||||||
|
return np;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packageReceived(NetworkPackage np) throws Exception{
|
||||||
|
|
||||||
|
boolean wantsPair = np.getBoolean("pair");
|
||||||
|
|
||||||
|
if (wantsPair == isPaired()) {
|
||||||
|
if (mPairStatus == PairStatus.Requested) {
|
||||||
|
//Log.e("Device","Unpairing (pair rejected)");
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
hidePairingNotification();
|
||||||
|
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsPair) {
|
||||||
|
|
||||||
|
if (mPairStatus == PairStatus.Requested) { //We started pairing
|
||||||
|
hidePairingNotification();
|
||||||
|
pairingDone();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// If device is already paired, accept pairing silently
|
||||||
|
if (mDevice.isPaired()) {
|
||||||
|
acceptPairing();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pairing notifications are still managed by device as there is no other way to
|
||||||
|
// know about notificationId to cancel notification when PairActivity is started
|
||||||
|
// Even putting notificationId in intent does not work because PairActivity can be
|
||||||
|
// started from MainActivity too, so then notificationId cannot be set
|
||||||
|
hidePairingNotification();
|
||||||
|
mDevice.displayPairingNotification();
|
||||||
|
|
||||||
|
mPairingTimer = new Timer();
|
||||||
|
|
||||||
|
mPairingTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.w("KDE/Device","Unpairing (timeout B)");
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
hidePairingNotification();
|
||||||
|
}
|
||||||
|
}, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
|
||||||
|
mPairStatus = PairStatus.RequestedByPeer;
|
||||||
|
mCallback.incomingRequest();
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.i("KDE/Pairing", "Unpair request");
|
||||||
|
|
||||||
|
if (mPairStatus == PairStatus.Requested) {
|
||||||
|
hidePairingNotification();
|
||||||
|
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
|
||||||
|
} else if (mPairStatus == PairStatus.Paired) {
|
||||||
|
mCallback.unpaired();
|
||||||
|
}
|
||||||
|
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestPairing() {
|
||||||
|
|
||||||
|
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
hidePairingNotification(); //Will stop the pairingTimer if it was running
|
||||||
|
mPairingTimer = new Timer();
|
||||||
|
mPairingTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
|
||||||
|
Log.w("KDE/Device","Unpairing (timeout A)");
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
}
|
||||||
|
}, 30*1000); //Time to wait for the other to accept
|
||||||
|
mPairStatus = PairStatus.Requested;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mDevice.sendPackage(createPairPackage(), statusCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hidePairingNotification() {
|
||||||
|
mDevice.hidePairingNotification();
|
||||||
|
if (mPairingTimer != null) {
|
||||||
|
mPairingTimer .cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acceptPairing() {
|
||||||
|
hidePairingNotification();
|
||||||
|
Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
pairingDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mDevice.sendPackage(createPairPackage(), statusCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rejectPairing() {
|
||||||
|
hidePairingNotification();
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||||
|
np.set("pair", false);
|
||||||
|
mDevice.sendPackage(np);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
public void pairingDone() {
|
||||||
|
// Store device information needed to create a Device object in a future
|
||||||
|
//Log.e("KDE/PairingDone", "Pairing Done");
|
||||||
|
mPairStatus = PairStatus.Paired;
|
||||||
|
mCallback.pairingDone();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unpair() {
|
||||||
|
mPairStatus = PairStatus.NotPaired;
|
||||||
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||||
|
np.set("pair", false);
|
||||||
|
mDevice.sendPackage(np);
|
||||||
|
}
|
||||||
|
}
|
@ -26,11 +26,13 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
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.SecurityHelpers.RsaHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
@ -140,11 +142,9 @@ public class BackgroundService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void registerLinkProviders() {
|
private void registerLinkProviders() {
|
||||||
|
|
||||||
//linkProviders.add(new LoopbackLinkProvider(this));
|
|
||||||
|
|
||||||
linkProviders.add(new LanLinkProvider(this));
|
linkProviders.add(new LanLinkProvider(this));
|
||||||
|
// linkProviders.add(new LoopbackLinkProvider(this));
|
||||||
|
// linkProviders.add(new BluetoothLinkProvider(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<BaseLinkProvider> getLinkProviders() {
|
public ArrayList<BaseLinkProvider> getLinkProviders() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user