blob: c97a055281896fcb0d8619e141ce2cc846efa665 [file] [log] [blame]
package com.android.clockwork.bluetooth;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_ANDROID;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.clockwork.common.WearBluetoothSettings;
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.Set;
/**
* This class monitors and maintains the mapping to the currently paired Companion device.
*
* This class expects the PairingHandler in ClockworkHome to set the Companion address
* under wear settings provider when the user initiates a successful pairing to the watch
* from the Companion app.
*
* For the legacy case of there being a previously-paired device with no Companion address
* written in Settings, CompanionTracker will infer the Companion device and store its address
* into the same Settings location.
*
* Design doc:
* https://docs.google.com/document/d/1E9-wBGZqHCB5Y7hJ-JI6ktb84tlG3e25AIazGOp7kEI/edit#
*/
public class CompanionTracker {
public static final String TAG = WearBluetoothSettings.LOG_TAG;
/** Callback when the companion has paired or unpaired to the watch */
public interface Listener {
void onCompanionChanged();
}
private final ContentResolver mContentResolver;
@Nullable private final BluetoothAdapter mBtAdapter;
@VisibleForTesting final SettingsObserver mSettingsObserver;
private final Set<Listener> mListeners;
private BluetoothDevice mCompanion;
private int mCompanionOsType = PAIRED_DEVICE_OS_TYPE_UNKNOWN;
public CompanionTracker(ContentResolver contentResolver, BluetoothAdapter btAdapter) {
mContentResolver = contentResolver;
mBtAdapter = btAdapter;
mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper()));
mListeners = new HashSet<>();
mCompanion = null;
contentResolver.registerContentObserver(
WearBluetoothSettings.BLUETOOTH_URI,
false,
mSettingsObserver);
}
public void addListener(Listener listener) {
mListeners.add(listener);
}
/**
* Returns the BluetoothDevice object associated with the Companion device,
* or null if no Companion device is paired.
*/
public BluetoothDevice getCompanion() {
return mCompanion;
}
/**
* Returns the BluetoothDevice name associated with the Companion device,
* or null if no Companion device is paired.
*/
public String getCompanionName() {
if (mCompanion != null) {
return mCompanion.getName();
}
return null;
}
/** Returns the BluetoothDevice address associated with the Companion device. */
public String getCompanionAddress() {
if (mCompanion != null) {
return mCompanion.getAddress();
} else {
return getStringValueForKey(WearBluetoothSettings.KEY_COMPANION_ADDRESS, "");
}
}
/**
* Returns the BluetoothDevice that associated with the Companion device.
*
* <p><b>Note:</b> On Android T, this method will return the same {@link BluetoothDevice} as
* {@link CompanionTracker#getCompanion}.
*
* <p><b>See:</b> {@link #getCompanion}.
*/
public @Nullable BluetoothDevice getBluetoothClassicCompanion() {
return getCompanion();
}
/**
* Returns true iff the currently paired Companion device is an LE or DUAL device.
*
* <p>Normally, we should just check the device type of mCompanion. But b/62355127 revealed that
* BluetoothDevice.getType() can return LE/DUAL even for an Android device (particularly when
* bonding is unexpectedly lost and re-established). To workaround this, we rely on the
* Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE setting written by ConnectionSetupHelper in
* Setup, which during the initial pairing process correctly identifies the device type.
*
* <p>BluetoothDevice.getType() is used as a fallback only if for some reason the
* Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE setting has not been populated.
*/
public boolean isCompanionBle() {
if (mCompanion == null) {
return false;
}
if (mCompanionOsType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
return mCompanionOsType == PAIRED_DEVICE_OS_TYPE_IOS;
}
boolean deviceIsBle = mCompanion.getType() == BluetoothDevice.DEVICE_TYPE_LE
|| mCompanion.getType() == BluetoothDevice.DEVICE_TYPE_DUAL;
mCompanionOsType =
getIntValueForKey(
PAIRED_DEVICE_OS_TYPE,
PAIRED_DEVICE_OS_TYPE_UNKNOWN);
if (mCompanionOsType == PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
Log.e(
TAG,
"Paired companion device OS type is unknown. "
+ " Relying on device type instead: " + mCompanion.getType());
return deviceIsBle;
}
boolean legacyModeIsBle = (mCompanionOsType == PAIRED_DEVICE_OS_TYPE_IOS);
if (legacyModeIsBle != deviceIsBle) {
Log.w(TAG, "Legacy BT Mode derived from OS type is different from paired device type. "
+ "Paired device mode: " + mCompanion.getType() + "; "
+ "OS type: " + mCompanionOsType);
}
return legacyModeIsBle;
}
/**
* This needs to be called once per reboot in order to guarantee that CompanionTracker
* will have the correct Companion Device retrieved from persistent storage and the Bluetooth
* adapter.
*/
public void onBluetoothAdapterReady() {
if (mBtAdapter == null) {
return;
}
String companionAddress =
getStringValueForKey(WearBluetoothSettings.KEY_COMPANION_ADDRESS, null);
if (companionAddress != null && !companionAddress.isEmpty()) {
Log.d(TAG, "Loading companion by address from settings: " + companionAddress);
updateCompanionDevice(companionAddress);
return;
}
// if we got to here, we're either not paired or we need to migrate from an existing device
Set<BluetoothDevice> bondedDevices = mBtAdapter.getBondedDevices();
if (bondedDevices.isEmpty()) {
// we're not paired, so just bail
return;
}
// retrieve the pairing state indirectly by checking the paired device OS type.
int pairedDeviceOSType =
getIntValueForKey(
PAIRED_DEVICE_OS_TYPE,
PAIRED_DEVICE_OS_TYPE_UNKNOWN);
Log.d(
TAG,
"Migrating legacy Companion address with paired device OS type : "
+ pairedDeviceOSType);
if (pairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_IOS) {
for (BluetoothDevice device : bondedDevices) {
if (device.getType() == BluetoothDevice.DEVICE_TYPE_LE
|| device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL) {
mCompanion = device;
break;
}
}
} else if (pairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_ANDROID) {
for (BluetoothDevice device : bondedDevices) {
if (device.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC) {
final BluetoothClass btClass = device.getBluetoothClass();
if (btClass != null && btClass.getMajorDeviceClass()
== BluetoothClass.Device.Major.PHONE) {
mCompanion = device;
break;
}
}
}
} else if (pairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
// When upgrading from E, add the first android device as companion.
for (BluetoothDevice device : bondedDevices) {
if (device.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC) {
final BluetoothClass btClass = device.getBluetoothClass();
if (btClass != null && btClass.getMajorDeviceClass() ==
BluetoothClass.Device.Major.PHONE) {
mCompanion = device;
break;
}
}
}
if (mCompanion == null) {
// add the first LE device as companion if we can't find android device.
for (BluetoothDevice device : bondedDevices) {
if (device.getType() == BluetoothDevice.DEVICE_TYPE_LE ||
device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL) {
mCompanion = device;
break;
}
}
}
}
// we found a legacy Companion pairing. update the database
if (mCompanion != null) {
Log.d(TAG, "legacy companion address: " + mCompanion.getAddress());
WearBluetoothSettings.putString(
mContentResolver,
WearBluetoothSettings.KEY_COMPANION_ADDRESS,
mCompanion.getAddress());
}
}
/**
* A bluetooth device has just bonded.
*
* Check if the newly bonded device is our companion and notify
* if we don't already have a companion initialized.
*/
void receivedBondedAction(@NonNull final BluetoothDevice device) {
final String companionAddress =
getStringValueForKey(WearBluetoothSettings.KEY_COMPANION_ADDRESS, null);
if (mCompanion == null && device.getAddress().equals(companionAddress)) {
int osType =
getIntValueForKey(
PAIRED_DEVICE_OS_TYPE,
PAIRED_DEVICE_OS_TYPE_UNKNOWN);
notifyIfCompanionChanged(device.getAddress(), osType);
}
}
/**
* Listens for changes to the Bluetooth Settings and updates our pointer to the
* currently-paired Companion device if necessary.
*/
final class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
public void onChange(boolean selfChange, Uri uri) {
String newCompanionAddress =
getStringValueForKey(WearBluetoothSettings.KEY_COMPANION_ADDRESS, null);
int osType = getIntValueForKey(
PAIRED_DEVICE_OS_TYPE,
PAIRED_DEVICE_OS_TYPE_UNKNOWN);
Log.d(TAG, "Companion address Settings update: " + newCompanionAddress
+ "; type: " + osType);
notifyIfCompanionChanged(newCompanionAddress, osType);
}
}
private void notifyIfCompanionChanged(
@Nullable final String newCompanionAddress,
int newOsType) {
if (newCompanionAddress != null && newOsType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
if (updateCompanionDevice(newCompanionAddress) || newOsType != mCompanionOsType) {
mCompanionOsType = newOsType;
if (mCompanion == null) {
Log.d(TAG, "Companion updated skipped: waiting BOND");
return;
}
for (Listener listener : mListeners) {
listener.onCompanionChanged();
}
}
}
}
/**
* Returns true if the specified bluetooth device address matches a
* currently bonded device. If they match the companion
* device address is updated to point to the specified bluetooth device.
*
* @param newDeviceAddr specified bluetooth device address.
* @return
*/
private boolean updateCompanionDevice(final String newDeviceAddr) {
if (mBtAdapter == null) {
return false;
}
if (mCompanion != null && mCompanion.getAddress().equals(newDeviceAddr)) {
return false;
}
boolean updated = false;
for (BluetoothDevice device : mBtAdapter.getBondedDevices()) {
if (device.getAddress().equals(newDeviceAddr)) {
mCompanion = device;
updated = true;
}
}
return updated;
}
private String getStringValueForKey(String key, String defaultValue) {
String val = WearBluetoothSettings.getString(mContentResolver, key);
if (val == null) {
return defaultValue;
} else {
return val;
}
}
private int getIntValueForKey(String key, int defaultValue) {
return WearBluetoothSettings.getInt(mContentResolver, key, defaultValue);
}
/** Returns true if device's list of cached UUID's contains the given uuid. */
static boolean isUuidPresent(BluetoothDevice device, ParcelUuid uuid) {
ParcelUuid[] uuidArray = device.getUuids();
if ((uuidArray == null || uuidArray.length == 0) && uuid == null) {
return true;
}
if (uuidArray == null) {
return false;
}
for (ParcelUuid element : uuidArray) {
if (element.equals(uuid)) {
return true;
}
}
return false;
}
}