blob: 20ece69d7281bfa683d4b8e68f6f8cc18bd2906e [file] [log] [blame]
/*
* Copyright (C) 2018 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.android.settingslib.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
*/
public class HearingAidDeviceManager {
private static final String TAG = "HearingAidDeviceManager";
private static final boolean DEBUG = BluetoothUtils.D;
private final LocalBluetoothManager mBtManager;
private final List<CachedBluetoothDevice> mCachedDevices;
HearingAidDeviceManager(LocalBluetoothManager localBtManager,
List<CachedBluetoothDevice> CachedDevices) {
mBtManager = localBtManager;
mCachedDevices = CachedDevices;
}
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
long hiSyncId = getHiSyncId(newDevice.getDevice());
if (isValidHiSyncId(hiSyncId)) {
// Once hiSyncId is valid, assign hiSyncId
newDevice.setHiSyncId(hiSyncId);
}
}
private long getHiSyncId(BluetoothDevice device) {
LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
if (profileProxy != null) {
return profileProxy.getHiSyncId(device);
}
return BluetoothHearingAid.HI_SYNC_ID_INVALID;
}
boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
final long hiSyncId = newDevice.getHiSyncId();
if (isValidHiSyncId(hiSyncId)) {
final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId);
// Just add one of the hearing aids from a pair in the list that is shown in the UI.
// Once there is another device with the same hiSyncId, to add new device as sub
// device.
if (hearingAidDevice != null) {
hearingAidDevice.setSubDevice(newDevice);
return true;
}
}
return false;
}
private boolean isValidHiSyncId(long hiSyncId) {
return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
}
private CachedBluetoothDevice getCachedDevice(long hiSyncId) {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
if (cachedDevice.getHiSyncId() == hiSyncId) {
return cachedDevice;
}
}
return null;
}
// To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
void updateHearingAidsDevices() {
final Set<Long> newSyncIdSet = new HashSet<Long>();
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
// Do nothing if HiSyncId has been assigned
if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
// Do nothing if there is no HiSyncId on Bluetooth device
if (isValidHiSyncId(newHiSyncId)) {
cachedDevice.setHiSyncId(newHiSyncId);
newSyncIdSet.add(newHiSyncId);
}
}
}
for (Long syncId : newSyncIdSet) {
onHiSyncIdChanged(syncId);
}
}
// Group devices by hiSyncId
@VisibleForTesting
void onHiSyncIdChanged(long hiSyncId) {
int firstMatchedIndex = -1;
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
if (cachedDevice.getHiSyncId() != hiSyncId) {
continue;
}
if (firstMatchedIndex == -1) {
// Found the first one
firstMatchedIndex = i;
continue;
}
// Found the second one
int indexToRemoveFromUi;
CachedBluetoothDevice subDevice;
CachedBluetoothDevice mainDevice;
// Since the hiSyncIds have been updated for a connected pair of hearing aids,
// we remove the entry of one the hearing aids from the UI. Unless the
// hiSyncId get updated, the system does not know it is a hearing aid, so we add
// both the hearing aids as separate entries in the UI first, then remove one
// of them after the hiSyncId is populated. We will choose the device that
// is not connected to be removed.
if (cachedDevice.isConnected()) {
mainDevice = cachedDevice;
indexToRemoveFromUi = firstMatchedIndex;
subDevice = mCachedDevices.get(firstMatchedIndex);
} else {
mainDevice = mCachedDevices.get(firstMatchedIndex);
indexToRemoveFromUi = i;
subDevice = cachedDevice;
}
mainDevice.setSubDevice(subDevice);
mCachedDevices.remove(indexToRemoveFromUi);
log("onHiSyncIdChanged: removed from UI device =" + subDevice
+ ", with hiSyncId=" + hiSyncId);
mBtManager.getEventManager().dispatchDeviceRemoved(subDevice);
break;
}
}
// @return {@code true}, the event is processed inside the method. It is for updating
// hearing aid device on main-sub relationship when receiving connected or disconnected.
// @return {@code false}, it is not hearing aid device or to process it same as other profiles
boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice,
int state) {
switch (state) {
case BluetoothProfile.STATE_CONNECTED:
onHiSyncIdChanged(cachedDevice.getHiSyncId());
CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
if (mainDevice != null){
if (mainDevice.isConnected()) {
// When main device exists and in connected state, receiving sub device
// connection. To refresh main device UI
mainDevice.refresh();
return true;
} else {
// When both Hearing Aid devices are disconnected, receiving sub device
// connection. To switch content and dispatch to notify UI change
mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
mainDevice.switchSubDeviceContent();
mainDevice.refresh();
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
return true;
}
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
mainDevice = findMainDevice(cachedDevice);
if (mainDevice != null) {
// When main device exists, receiving sub device disconnection
// To update main device UI
mainDevice.refresh();
return true;
}
CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
if (subDevice != null && subDevice.isConnected()) {
// Main device is disconnected and sub device is connected
// To copy data from sub device to main device
mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
cachedDevice.switchSubDeviceContent();
cachedDevice.refresh();
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
return true;
}
break;
}
return false;
}
CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
if (subDevice != null && subDevice.equals(device)) {
return cachedDevice;
}
}
}
return null;
}
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
}
}
}