Skip to content

Commit 6fb0195

Browse files
author
SBALAVIGNESH123
committed
Add Bluetooth support for WearOS devices
Implements RFCOMM transport layer for WearOS connectivity without Google Play Services. - Created BluetoothWearableConnection for Bluetooth Classic transport - Added automatic device discovery and connection in WearableImpl - Added necessary Bluetooth permissions to manifest Addresses #2843 - + bounty for WearOS support
1 parent 6f0dd33 commit 6fb0195

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

play-services-core/src/main/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@
138138

139139
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
140140
<uses-permission android:name="android.permission.INTERNET" />
141+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
142+
<uses-permission android:name="android.permission.BLUETOOTH" />
143+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
144+
<!-- For Android 12+ -->
145+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
146+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
147+
141148
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
142149
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
143150
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (C) 2013-2019 microG Project Team
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.microg.gms.wearable;
18+
19+
import android.bluetooth.BluetoothSocket;
20+
21+
import com.squareup.wire.Wire;
22+
23+
import org.microg.wearable.WearableConnection;
24+
import org.microg.wearable.proto.MessagePiece;
25+
26+
import java.io.DataInputStream;
27+
import java.io.DataOutputStream;
28+
import java.io.IOException;
29+
30+
/**
31+
* Bluetooth transport implementation for Wearable connections.
32+
* Uses RFCOMM sockets to communicate with WearOS devices over Bluetooth Classic.
33+
*/
34+
public class BluetoothWearableConnection extends WearableConnection {
35+
private final int MAX_PIECE_SIZE = 20 * 1024 * 1024; // 20MB limit
36+
private final BluetoothSocket socket;
37+
private final DataInputStream is;
38+
private final DataOutputStream os;
39+
40+
public BluetoothWearableConnection(BluetoothSocket socket, Listener listener) throws IOException {
41+
super(listener);
42+
this.socket = socket;
43+
this.is = new DataInputStream(socket.getInputStream());
44+
this.os = new DataOutputStream(socket.getOutputStream());
45+
}
46+
47+
@Override
48+
protected void writeMessagePiece(MessagePiece piece) throws IOException {
49+
byte[] bytes = piece.toByteArray();
50+
os.writeInt(bytes.length);
51+
os.write(bytes);
52+
}
53+
54+
@Override
55+
protected MessagePiece readMessagePiece() throws IOException {
56+
int len = is.readInt();
57+
if (len > MAX_PIECE_SIZE || len < 0) {
58+
throw new IOException("Piece size " + len + " exceeded limit of " + MAX_PIECE_SIZE + " bytes.");
59+
}
60+
byte[] bytes = new byte[len];
61+
is.readFully(bytes);
62+
return new Wire().parseFrom(bytes, MessagePiece.class);
63+
}
64+
65+
@Override
66+
public void close() throws IOException {
67+
socket.close();
68+
}
69+
}

play-services-wearable/core/src/main/java/org/microg/gms/wearable/WearableImpl.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import android.text.TextUtils;
2828
import android.util.Base64;
2929
import android.util.Log;
30+
import android.bluetooth.BluetoothAdapter;
31+
import android.bluetooth.BluetoothDevice;
32+
import android.bluetooth.BluetoothSocket;
3033

3134
import androidx.annotation.Nullable;
3235

@@ -69,13 +72,16 @@
6972
import java.util.List;
7073
import java.util.Map;
7174
import java.util.Set;
75+
import java.util.UUID;
7276
import java.util.concurrent.CountDownLatch;
7377

7478
import okio.ByteString;
7579

7680
public class WearableImpl {
7781

7882
private static final String TAG = "GmsWear";
83+
// Standard Serial Port Profile UUID for Bluetooth Classic
84+
private static final UUID UUID_WEAR = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
7985

8086
private static final int WEAR_TCP_PORT = 5601;
8187

@@ -87,6 +93,7 @@ public class WearableImpl {
8793
private final Map<String, WearableConnection> activeConnections = new HashMap<String, WearableConnection>();
8894
private RpcHelper rpcHelper;
8995
private SocketConnectionThread sct;
96+
private ConnectionThread btThread;
9097
private ConnectionConfiguration[] configurations;
9198
private boolean configurationsUpdated = false;
9299
private ClockworkNodePreferences clockworkNodePreferences;
@@ -105,6 +112,13 @@ public WearableImpl(Context context, NodeDatabaseHelper nodeDatabase, Configurat
105112
networkHandlerLock.countDown();
106113
Looper.loop();
107114
}).start();
115+
116+
// Start Bluetooth connection thread if Bluetooth is available
117+
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
118+
if (btAdapter != null) {
119+
btThread = new ConnectionThread();
120+
btThread.start();
121+
}
108122
}
109123

110124
public String getLocalNodeId() {
@@ -628,6 +642,10 @@ public void stop() {
628642
} catch (InterruptedException e) {
629643
Log.w(TAG, e);
630644
}
645+
if (btThread != null) {
646+
btThread.interrupt();
647+
btThread = null;
648+
}
631649
}
632650

633651
private class ListenerInfo {
@@ -639,4 +657,90 @@ private ListenerInfo(IWearableListener listener, IntentFilter[] filters) {
639657
this.filters = filters;
640658
}
641659
}
660+
661+
private class ConnectionThread extends Thread {
662+
@Override
663+
public void run() {
664+
while (!isInterrupted()) {
665+
try {
666+
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
667+
if (adapter != null && adapter.isEnabled()) {
668+
Set<BluetoothDevice> bondedDevices = adapter.getBondedDevices();
669+
for (BluetoothDevice device : bondedDevices) {
670+
// Skip if already connected
671+
if (activeConnections.containsKey(device.getAddress())) {
672+
continue;
673+
}
674+
675+
Log.d(TAG, "Attempting BT connection to " + device.getName() + " (" + device.getAddress() + ")");
676+
677+
try {
678+
// Create RFCOMM socket using SPP UUID
679+
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(UUID_WEAR);
680+
socket.connect();
681+
682+
if (socket.isConnected()) {
683+
Log.d(TAG, "Successfully connected via Bluetooth to " + device.getName());
684+
685+
// Create wearable connection wrapper
686+
BluetoothWearableConnection connection = new BluetoothWearableConnection(
687+
socket,
688+
new WearableConnection.Listener() {
689+
@Override
690+
public void onConnected(WearableConnection connection) {
691+
// Connection established, wait for handshake
692+
}
693+
694+
@Override
695+
public void onMessage(WearableConnection connection, RootMessage message) {
696+
// Handle incoming protocol messages
697+
if (message.connect != null) {
698+
onConnectReceived(connection, message.connect.id, message.connect);
699+
} else if (message.filePiece != null) {
700+
handleFilePiece(connection, message.filePiece.fileName,
701+
message.filePiece.piece.toByteArray(), message.filePiece.digest);
702+
} else {
703+
Log.d(TAG, "Received message: " + message);
704+
}
705+
}
706+
707+
@Override
708+
public void onDisconnected() {
709+
// Cleanup handled by existing logic
710+
}
711+
}
712+
);
713+
714+
// Start message processing thread
715+
new Thread(connection).start();
716+
717+
// Send our identity to the watch
718+
String localId = getLocalNodeId();
719+
connection.writeMessage(
720+
new RootMessage.Builder()
721+
.connect(new Connect.Builder()
722+
.id(localId)
723+
.name("Phone")
724+
.build())
725+
.build()
726+
);
727+
}
728+
} catch (IOException e) {
729+
Log.d(TAG, "BT connection failed: " + e.getMessage());
730+
}
731+
}
732+
}
733+
} catch (Exception e) {
734+
Log.w(TAG, "Error in Bluetooth ConnectionThread", e);
735+
}
736+
737+
// Wait before next scan attempt
738+
try {
739+
Thread.sleep(10000);
740+
} catch (InterruptedException e) {
741+
break;
742+
}
743+
}
744+
}
745+
}
642746
}

0 commit comments

Comments
 (0)