Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,965 changes: 1,493 additions & 1,472 deletions play-services-core/src/main/AndroidManifest.xml

Large diffs are not rendered by default.

113 changes: 57 additions & 56 deletions play-services-wearable/core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
apply plugin: 'signing'

dependencies {
implementation project(':play-services-base-core')

implementation project(':play-services-location')
implementation project(':play-services-wearable')

implementation "org.microg:wearable:$wearableVersion"
}

android {
namespace "org.microg.gms.wearable.core"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

buildFeatures {
dataBinding = true
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

lintOptions {
disable 'MissingTranslation'
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = 1.8
}
}

apply from: '../../gradle/publish-android.gradle'

description = 'microG service implementation for play-services-wearable'
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
apply plugin: 'signing'

dependencies {
implementation project(':play-services-base-core')

implementation project(':play-services-location')
implementation project(':play-services-wearable')

implementation "org.microg:wearable:$wearableVersion"
implementation "com.squareup.wire:wire-runtime:$wireVersion"
}

android {
namespace "org.microg.gms.wearable.core"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

buildFeatures {
dataBinding = true
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

lintOptions {
disable 'MissingTranslation'
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = 1.8
}
}

apply from: '../../gradle/publish-android.gradle'

description = 'microG service implementation for play-services-wearable'
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2024, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.wearable;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

import org.microg.wearable.WearableConnection;

import java.io.IOException;
import java.util.UUID;

/**
* Bluetooth RFCOMM server that accepts incoming connections from Wear OS watches.
* <p>
* Wear OS watches use Bluetooth SPP (Serial Port Profile) with the standard
* SPP UUID (00001101-0000-1000-8000-00805F9B34FB) or Wear OS-specific UUIDs.
*/
public class BluetoothConnectionServer extends Thread {

private static final String TAG = "GmsWearBtSrv";

/**
* Standard SPP UUID used by Wear OS for initial Bluetooth pairing and communication.
*/
private static final UUID WEAROS_SPP_UUID =
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

/**
* Additional Wear OS-specific UUID for Google's Wearable protocol.
*/
private static final UUID WEAROS_GOOGLE_UUID =
UUID.fromString("00000000-0000-1000-8000-00805F9B34FB");

private final String serviceName;
private final WearableConnection.Listener connectionListener;
private BluetoothServerSocket serverSocket;

public BluetoothConnectionServer(String serviceName, WearableConnection.Listener connectionListener) {
super("BluetoothConnectionServer");
this.serviceName = serviceName;
this.connectionListener = connectionListener;
}

@Override
public void run() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
Log.w(TAG, "Bluetooth not available on this device");
return;
}

if (!adapter.isEnabled()) {
Log.w(TAG, "Bluetooth is not enabled, cannot start WearOS server");
return;
}

// Try to listen on the standard SPP UUID first, fall back to Google UUID
BluetoothServerSocket socket = null;
try {
// Ensure device is discoverable for Wear OS companion app pairing
if (adapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Log.d(TAG, "Requesting discoverable mode for WearOS pairing...");
// Note: actual discoverable request requires user consent via intent
// This is handled by the calling activity/notification
}

// Listen on the SPP UUID for Wear OS connections
socket = adapter.listenUsingRfcommWithServiceRecord(serviceName, WEAROS_SPP_UUID);
serverSocket = socket;
Log.d(TAG, "Bluetooth server listening on " + WEAROS_SPP_UUID);

while (!Thread.interrupted()) {
BluetoothSocket clientSocket = socket.accept();
if (clientSocket != null) {
Log.d(TAG, "Accepted Bluetooth connection from: "
+ clientSocket.getRemoteDevice().getName()
+ " [" + clientSocket.getRemoteDevice().getAddress() + "]");
BluetoothWearableConnection connection =
new BluetoothWearableConnection(clientSocket, connectionListener);
// Start the connection in a new thread for each client
new Thread(connection, "BtConn-" + clientSocket.getRemoteDevice().getAddress()).start();
}
}
} catch (IOException e) {
if (!Thread.interrupted()) {
Log.e(TAG, "Bluetooth server error", e);
}
} finally {
closeSocket();
}
}

public void close() {
interrupt();
closeSocket();
}

private void closeSocket() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException ignored) {
}
serverSocket = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2024, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.wearable;

import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.squareup.wire.ProtoAdapter;

import org.microg.wearable.WearableConnection;
import org.microg.wearable.proto.MessagePiece;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
* WearableConnection that uses Bluetooth RFCOMM (SPP) transport.
* This is the primary transport used by Wear OS watches for phone pairing.
*/
public class BluetoothWearableConnection extends WearableConnection {

private static final String TAG = "GmsWearBtConn";
private static final int MAX_PIECE_SIZE = 20 * 1024 * 1024;

private final BluetoothSocket socket;
private final DataInputStream is;
private final DataOutputStream os;

public BluetoothWearableConnection(BluetoothSocket socket, Listener listener) throws IOException {
super(listener);
this.socket = socket;
this.is = new DataInputStream(socket.getInputStream());
this.os = new DataOutputStream(socket.getOutputStream());
}

@Override
protected void writeMessagePiece(MessagePiece piece) throws IOException {
byte[] bytes = piece.encode();
os.writeInt(bytes.length);
os.write(bytes);
os.flush();
}

@Override
protected MessagePiece readMessagePiece() throws IOException {
int len = is.readInt();
if (len > MAX_PIECE_SIZE) {
throw new IOException("Piece size " + len + " exceeded limit of " + MAX_PIECE_SIZE + " bytes.");
}
Log.d(TAG, "Reading piece of length " + len);
byte[] bytes = new byte[len];
is.readFully(bytes);
return ProtoAdapter.get(MessagePiece.class).decode(bytes);
}

@Override
public void close() throws IOException {
try {
socket.close();
} catch (IOException e) {
Log.w(TAG, "Error closing Bluetooth socket", e);
}
}
}
Loading