blob: 6e7aec51a99f7ab53e79b9d48a40bcc7e373de1b [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.connecteddevice.oob;
import static com.google.android.connecteddevice.util.SafeLog.logd;
import static com.google.android.connecteddevice.util.SafeLog.loge;
import static com.google.android.connecteddevice.util.SafeLog.logw;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.ParcelUuid;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.connecteddevice.model.OobEligibleDevice;
import com.google.android.connecteddevice.transport.spp.ConnectedDeviceSppDelegateBinder;
import com.google.android.connecteddevice.transport.spp.Connection;
import com.google.android.connecteddevice.transport.spp.PendingConnection;
import com.google.android.connecteddevice.transport.spp.PendingSentMessage;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/** Handles out of band data exchange over a secure RFCOMM channel. */
public class BluetoothRfcommChannel implements OobChannel {
private static final String TAG = "BluetoothRfcommChannel";
// TODO(b/159500330): Generate random UUID.
private static final UUID RFCOMM_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private final AtomicBoolean isInterrupted = new AtomicBoolean();
private final ConnectedDeviceSppDelegateBinder sppDelegateBinder;
private Connection activeConnection;
@VisibleForTesting Callback callback;
public BluetoothRfcommChannel(ConnectedDeviceSppDelegateBinder sppDelegateBinder) {
this.sppDelegateBinder = sppDelegateBinder;
}
@Override
public void completeOobDataExchange(
@NonNull OobEligibleDevice device, @NonNull Callback callback) {
completeOobDataExchange(device, callback, BluetoothAdapter.getDefaultAdapter());
}
@VisibleForTesting
void completeOobDataExchange(
OobEligibleDevice device, Callback callback, BluetoothAdapter bluetoothAdapter) {
this.callback = callback;
BluetoothDevice remoteDevice = bluetoothAdapter.getRemoteDevice(device.getDeviceAddress());
try {
PendingConnection connection =
sppDelegateBinder.connectAsClient(RFCOMM_UUID, remoteDevice, /* isSecure= */ true);
if (connection == null) {
notifyFailure("Connection with " + remoteDevice.getName() + " failed.", null);
return;
}
connection.setOnConnectedListener(
(uuid, btDevice, isSecure, deviceName) -> {
activeConnection = new Connection(new ParcelUuid(uuid), btDevice, isSecure, deviceName);
notifySuccess();
});
} catch (RemoteException e) {
notifyFailure("Connection with " + remoteDevice.getName() + " failed.", e);
}
}
@Override
public void sendOobData(byte[] oobData) {
if (isInterrupted.get()) {
logd(TAG, "Oob connection is interrupted, data will not be set.");
return;
}
if (activeConnection == null) {
notifyFailure("Connection is null, oob data cannot be sent", /* exception= */ null);
return;
}
try {
PendingSentMessage pendingSentMessage =
sppDelegateBinder.sendMessage(activeConnection, oobData);
if (pendingSentMessage == null) {
notifyFailure("Sending oob data failed", null);
return;
}
pendingSentMessage.setOnSuccessListener(
() -> {
try {
disconnect();
} catch (RemoteException e) {
logw(TAG, "Sending oob data succeeded, but disconnect failed");
}
});
} catch (RemoteException e) {
notifyFailure("Sending oob data failed", e);
}
}
@Override
public void interrupt() {
logd(TAG, "Interrupt received.");
isInterrupted.set(true);
try {
disconnect();
} catch (RemoteException e) {
loge(TAG, "Disconnect failed", e);
}
}
private void disconnect() throws RemoteException {
if (activeConnection != null) {
sppDelegateBinder.disconnect(activeConnection);
activeConnection = null;
}
// TODO(b/169876111): Call to sppDelegateBinder.cancelConnectionAttempt
}
private void notifyFailure(@NonNull String message, @Nullable Exception exception) {
loge(TAG, message, exception);
if (callback != null && !isInterrupted.get()) {
callback.onOobExchangeFailure();
}
}
private void notifySuccess() {
if (callback != null && !isInterrupted.get()) {
callback.onOobExchangeSuccess();
}
}
}