1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.sound;
18 
19 import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
20 
21 import android.bluetooth.BluetoothDevice;
22 import android.content.Context;
23 import android.text.TextUtils;
24 
25 import androidx.preference.ListPreference;
26 import androidx.preference.Preference;
27 
28 import com.android.settings.R;
29 import com.android.settingslib.Utils;
30 import com.android.settingslib.bluetooth.HeadsetProfile;
31 import com.android.settingslib.bluetooth.HearingAidProfile;
32 
33 /**
34  * This class allows switching between HFP-connected & HAP-connected BT devices
35  * while in on-call state.
36  */
37 public class HandsFreeProfileOutputPreferenceController extends AudioSwitchPreferenceController
38         implements Preference.OnPreferenceChangeListener {
39 
40     private static final int INVALID_INDEX = -1;
41 
HandsFreeProfileOutputPreferenceController(Context context, String key)42     public HandsFreeProfileOutputPreferenceController(Context context, String key) {
43         super(context, key);
44     }
45 
46     @Override
onPreferenceChange(Preference preference, Object newValue)47     public boolean onPreferenceChange(Preference preference, Object newValue) {
48         final String address = (String) newValue;
49         if (!(preference instanceof ListPreference)) {
50             return false;
51         }
52 
53         final CharSequence defaultSummary = mContext.getText(R.string.media_output_default_summary);
54         final ListPreference listPreference = (ListPreference) preference;
55         if (TextUtils.equals(address, defaultSummary)) {
56             // Switch to default device which address is device name
57             mSelectedIndex = getDefaultDeviceIndex();
58             setActiveBluetoothDevice(null);
59             listPreference.setSummary(defaultSummary);
60         } else {
61             // Switch to BT device which address is hardware address
62             final int connectedDeviceIndex = getConnectedDeviceIndex(address);
63             if (connectedDeviceIndex == INVALID_INDEX) {
64                 return false;
65             }
66             final BluetoothDevice btDevice = mConnectedDevices.get(connectedDeviceIndex);
67             mSelectedIndex = connectedDeviceIndex;
68             setActiveBluetoothDevice(btDevice);
69             listPreference.setSummary(btDevice.getAlias());
70         }
71         return true;
72     }
73 
getConnectedDeviceIndex(String hardwareAddress)74     private int getConnectedDeviceIndex(String hardwareAddress) {
75         if (mConnectedDevices != null) {
76             for (int i = 0, size = mConnectedDevices.size(); i < size; i++) {
77                 final BluetoothDevice btDevice = mConnectedDevices.get(i);
78                 if (TextUtils.equals(btDevice.getAddress(), hardwareAddress)) {
79                     return i;
80                 }
81             }
82         }
83         return INVALID_INDEX;
84     }
85 
86     @Override
updateState(Preference preference)87     public void updateState(Preference preference) {
88         if (preference == null) {
89             // In case UI is not ready.
90             return;
91         }
92 
93         if (!Utils.isAudioModeOngoingCall(mContext)) {
94             // Without phone call, disable the switch entry.
95             mPreference.setVisible(false);
96             preference.setSummary(mContext.getText(R.string.media_output_default_summary));
97             return;
98         }
99 
100         // Ongoing call status, list all the connected devices support hands free profile.
101         // Select current active device.
102         // Disable switch entry if there is no connected device.
103         mConnectedDevices.clear();
104         mConnectedDevices.addAll(getConnectedHfpDevices());
105         mConnectedDevices.addAll(getConnectedHearingAidDevices());
106         mConnectedDevices.addAll(getConnectedLeAudioDevices());
107 
108         final int numDevices = mConnectedDevices.size();
109         if (numDevices == 0) {
110             // No connected devices, disable switch entry.
111             mPreference.setVisible(false);
112             final CharSequence summary = mContext.getText(R.string.media_output_default_summary);
113             final CharSequence[] defaultMediaOutput = new CharSequence[]{summary};
114             mSelectedIndex = getDefaultDeviceIndex();
115             preference.setSummary(summary);
116             setPreference(defaultMediaOutput, defaultMediaOutput, preference);
117             return;
118         }
119 
120         mPreference.setVisible(true);
121         CharSequence[] mediaOutputs = new CharSequence[numDevices + 1];
122         CharSequence[] mediaValues = new CharSequence[numDevices + 1];
123 
124         // Setup devices entries, select active connected device
125         setupPreferenceEntries(mediaOutputs, mediaValues, findActiveDevice());
126 
127         // Display connected devices, default device and show the active device
128         setPreference(mediaOutputs, mediaValues, preference);
129     }
130 
getDefaultDeviceIndex()131     int getDefaultDeviceIndex() {
132         // Default device is after all connected devices.
133         return mConnectedDevices.size();
134     }
135 
setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues, BluetoothDevice activeDevice)136     void setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues,
137             BluetoothDevice activeDevice) {
138         // default to current device
139         mSelectedIndex = getDefaultDeviceIndex();
140         // default device is after all connected devices.
141         final CharSequence defaultSummary = mContext.getText(R.string.media_output_default_summary);
142         mediaOutputs[mSelectedIndex] = defaultSummary;
143         // use default device name as address
144         mediaValues[mSelectedIndex] = defaultSummary;
145         for (int i = 0, size = mConnectedDevices.size(); i < size; i++) {
146             final BluetoothDevice btDevice = mConnectedDevices.get(i);
147             mediaOutputs[i] = btDevice.getAlias();
148             mediaValues[i] = btDevice.getAddress();
149             if (btDevice.equals(activeDevice)) {
150                 // select the active connected device.
151                 mSelectedIndex = i;
152             }
153         }
154     }
155 
setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues, Preference preference)156     void setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues,
157             Preference preference) {
158         final ListPreference listPreference = (ListPreference) preference;
159         listPreference.setEntries(mediaOutputs);
160         listPreference.setEntryValues(mediaValues);
161         listPreference.setValueIndex(mSelectedIndex);
162         listPreference.setSummary(mediaOutputs[mSelectedIndex]);
163         mAudioSwitchPreferenceCallback.onPreferenceDataChanged(listPreference);
164     }
165 
setActiveBluetoothDevice(BluetoothDevice device)166     public void setActiveBluetoothDevice(BluetoothDevice device) {
167         if (!Utils.isAudioModeOngoingCall(mContext)) {
168             return;
169         }
170         final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
171         final HeadsetProfile hfpProfile = mProfileManager.getHeadsetProfile();
172         if (hapProfile != null && hfpProfile != null && device == null) {
173             hfpProfile.setActiveDevice(null);
174             hapProfile.setActiveDevice(null);
175         } else if (hapProfile != null && device != null
176                 && hapProfile.getHiSyncId(device) != HI_SYNC_ID_INVALID) {
177             hapProfile.setActiveDevice(device);
178         } else if (hfpProfile != null) {
179             hfpProfile.setActiveDevice(device);
180         }
181     }
182 
183     @Override
findActiveDevice()184     public BluetoothDevice findActiveDevice() {
185         BluetoothDevice haActiveDevice = findActiveHearingAidDevice();
186         BluetoothDevice leAudioActiveDevice = findActiveLeAudioDevice();
187         final HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
188 
189         if (haActiveDevice != null) {
190             return haActiveDevice;
191         }
192 
193         if (leAudioActiveDevice != null) {
194             return leAudioActiveDevice;
195         }
196 
197         if (headsetProfile != null && headsetProfile.getActiveDevice() != null) {
198             return headsetProfile.getActiveDevice();
199         }
200 
201         return null;
202     }
203 }
204