From ef6d379198cf8bf20fd2d523645217849473607a Mon Sep 17 00:00:00 2001 From: yueg Date: Wed, 18 Apr 2018 12:46:18 -0700 Subject: Multi-HFP device support for audio selector. Test: BluetoothDeviceProviderTest PiperOrigin-RevId: 193401073 Change-Id: I3483392d1d7a473a753477074b5c377af257186e --- .../audiomode/BluetoothDeviceProvider.java | 203 +++++++++++++++++++++ .../BluetoothDeviceProviderComponent.java | 39 ++++ 2 files changed, 242 insertions(+) create mode 100644 java/com/android/incallui/audiomode/BluetoothDeviceProvider.java create mode 100644 java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java (limited to 'java/com/android/incallui/audiomode') diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java b/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java new file mode 100644 index 000000000..1aa1c20a8 --- /dev/null +++ b/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java @@ -0,0 +1,203 @@ +/* + * 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.incallui.audiomode; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.ArraySet; +import com.android.dialer.common.LogUtil; +import com.android.dialer.inject.ApplicationContext; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Proxy class for getting and setting connected/active Bluetooth devices. */ +@Singleton +public final class BluetoothDeviceProvider extends BroadcastReceiver { + + // TODO(yueg): use BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED when possible + private static final String ACTION_ACTIVE_DEVICE_CHANGED = + "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; + + private final Context appContext; + private final BluetoothProfileServiceListener bluetoothProfileServiceListener = + new BluetoothProfileServiceListener(); + + private final Set connectedBluetoothDeviceSet = new ArraySet<>(); + + private BluetoothDevice activeBluetoothDevice; + private BluetoothHeadset bluetoothHeadset; + private boolean isSetUp; + + @Inject + public BluetoothDeviceProvider(@ApplicationContext Context appContext) { + this.appContext = appContext; + } + + public void setUp() { + if (BluetoothAdapter.getDefaultAdapter() == null) { + // Bluetooth is not supported on this hardware platform + return; + } + // Get Bluetooth service including the initial connected device list (should only contain one + // device) + BluetoothAdapter.getDefaultAdapter() + .getProfileProxy(appContext, bluetoothProfileServiceListener, BluetoothProfile.HEADSET); + // Get notified of Bluetooth device update + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(ACTION_ACTIVE_DEVICE_CHANGED); + appContext.registerReceiver(this, filter); + + isSetUp = true; + } + + public void tearDown() { + if (!isSetUp) { + return; + } + appContext.unregisterReceiver(this); + if (bluetoothHeadset != null) { + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); + } + } + + public Set getConnectedBluetoothDeviceSet() { + return connectedBluetoothDeviceSet; + } + + public BluetoothDevice getActiveBluetoothDevice() { + return activeBluetoothDevice; + } + + @SuppressLint("PrivateApi") + public void setActiveBluetoothDevice(BluetoothDevice bluetoothDevice) { + if (!connectedBluetoothDeviceSet.contains(bluetoothDevice)) { + LogUtil.e("BluetoothProfileServiceListener.setActiveBluetoothDevice", "device is not in set"); + return; + } + // TODO(yueg): use BluetoothHeadset.setActiveDevice() when possible + try { + Method getActiveDeviceMethod = + bluetoothHeadset.getClass().getDeclaredMethod("setActiveDevice", BluetoothDevice.class); + getActiveDeviceMethod.setAccessible(true); + getActiveDeviceMethod.invoke(bluetoothHeadset, bluetoothDevice); + } catch (Exception e) { + LogUtil.e( + "BluetoothProfileServiceListener.setActiveBluetoothDevice", + "failed to call setActiveDevice", + e); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { + handleActionConnectionStateChanged(intent); + } else if (ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { + handleActionActiveDeviceChanged(intent); + } + } + + private void handleActionConnectionStateChanged(Intent intent) { + if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) { + LogUtil.i( + "BluetoothDeviceProvider.handleActionConnectionStateChanged", + "extra BluetoothDevice.EXTRA_DEVICE not found"); + return; + } + BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (bluetoothDevice == null) { + return; + } + + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + if (state == BluetoothProfile.STATE_DISCONNECTED) { + connectedBluetoothDeviceSet.remove(bluetoothDevice); + LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device removed"); + } else if (state == BluetoothProfile.STATE_CONNECTED) { + connectedBluetoothDeviceSet.add(bluetoothDevice); + LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device added"); + } + } + + private void handleActionActiveDeviceChanged(Intent intent) { + if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) { + LogUtil.i( + "BluetoothDeviceProvider.handleActionActiveDeviceChanged", + "extra BluetoothDevice.EXTRA_DEVICE not found"); + return; + } + activeBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + LogUtil.i( + "BluetoothDeviceProvider.handleActionActiveDeviceChanged", + (activeBluetoothDevice == null ? "null" : "")); + } + + private final class BluetoothProfileServiceListener implements BluetoothProfile.ServiceListener { + @Override + @SuppressLint("PrivateApi") + public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) { + if (profile != BluetoothProfile.HEADSET) { + return; + } + // Get initial connected device list + bluetoothHeadset = (BluetoothHeadset) bluetoothProfile; + List devices = bluetoothProfile.getConnectedDevices(); + for (BluetoothDevice device : devices) { + connectedBluetoothDeviceSet.add(device); + LogUtil.i( + "BluetoothProfileServiceListener.onServiceConnected", "get initial connected device"); + } + + // Get initial active device + // TODO(yueg): use BluetoothHeadset.getActiveDevice() when possible + try { + Method getActiveDeviceMethod = + bluetoothHeadset.getClass().getDeclaredMethod("getActiveDevice"); + getActiveDeviceMethod.setAccessible(true); + activeBluetoothDevice = (BluetoothDevice) getActiveDeviceMethod.invoke(bluetoothHeadset); + LogUtil.i( + "BluetoothProfileServiceListener.onServiceConnected", + "get initial active device" + ((activeBluetoothDevice == null) ? " null" : "")); + } catch (Exception e) { + LogUtil.e( + "BluetoothProfileServiceListener.onServiceConnected", + "failed to call getAcitveDevice", + e); + } + } + + @Override + public void onServiceDisconnected(int profile) { + LogUtil.enterBlock("BluetoothProfileServiceListener.onServiceDisconnected"); + if (profile == BluetoothProfile.HEADSET) { + bluetoothHeadset = null; + } + } + } +} diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java b/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java new file mode 100644 index 000000000..9cd926835 --- /dev/null +++ b/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 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.incallui.audiomode; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +/** Dagger component for the Bluetooth device provider. */ +@Subcomponent +public abstract class BluetoothDeviceProviderComponent { + + public abstract BluetoothDeviceProvider bluetoothDeviceProvider(); + + public static BluetoothDeviceProviderComponent get(Context context) { + return ((BluetoothDeviceProviderComponent.HasComponent) + ((HasRootComponent) context.getApplicationContext()).component()) + .bluetoothDeviceProviderComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + BluetoothDeviceProviderComponent bluetoothDeviceProviderComponent(); + } +} -- cgit v1.2.3