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.IBluetoothHearingAid.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 
107         final int numDevices = mConnectedDevices.size();
108         if (numDevices == 0) {
109             // No connected devices, disable switch entry.
110             mPreference.setVisible(false);
111             final CharSequence summary = mContext.getText(R.string.media_output_default_summary);
112             final CharSequence[] defaultMediaOutput = new CharSequence[]{summary};
113             mSelectedIndex = getDefaultDeviceIndex();
114             preference.setSummary(summary);
115             setPreference(defaultMediaOutput, defaultMediaOutput, preference);
116             return;
117         }
118 
119         mPreference.setVisible(true);
120         CharSequence[] mediaOutputs = new CharSequence[numDevices + 1];
121         CharSequence[] mediaValues = new CharSequence[numDevices + 1];
122 
123         // Setup devices entries, select active connected device
124         setupPreferenceEntries(mediaOutputs, mediaValues, findActiveDevice());
125 
126         // Display connected devices, default device and show the active device
127         setPreference(mediaOutputs, mediaValues, preference);
128     }
129 
getDefaultDeviceIndex()130     int getDefaultDeviceIndex() {
131         // Default device is after all connected devices.
132         return mConnectedDevices.size();
133     }
134 
setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues, BluetoothDevice activeDevice)135     void setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues,
136             BluetoothDevice activeDevice) {
137         // default to current device
138         mSelectedIndex = getDefaultDeviceIndex();
139         // default device is after all connected devices.
140         final CharSequence defaultSummary = mContext.getText(R.string.media_output_default_summary);
141         mediaOutputs[mSelectedIndex] = defaultSummary;
142         // use default device name as address
143         mediaValues[mSelectedIndex] = defaultSummary;
144         for (int i = 0, size = mConnectedDevices.size(); i < size; i++) {
145             final BluetoothDevice btDevice = mConnectedDevices.get(i);
146             mediaOutputs[i] = btDevice.getAlias();
147             mediaValues[i] = btDevice.getAddress();
148             if (btDevice.equals(activeDevice)) {
149                 // select the active connected device.
150                 mSelectedIndex = i;
151             }
152         }
153     }
154 
setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues, Preference preference)155     void setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues,
156             Preference preference) {
157         final ListPreference listPreference = (ListPreference) preference;
158         listPreference.setEntries(mediaOutputs);
159         listPreference.setEntryValues(mediaValues);
160         listPreference.setValueIndex(mSelectedIndex);
161         listPreference.setSummary(mediaOutputs[mSelectedIndex]);
162         mAudioSwitchPreferenceCallback.onPreferenceDataChanged(listPreference);
163     }
164 
setActiveBluetoothDevice(BluetoothDevice device)165     public void setActiveBluetoothDevice(BluetoothDevice device) {
166         if (!Utils.isAudioModeOngoingCall(mContext)) {
167             return;
168         }
169         final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
170         final HeadsetProfile hfpProfile = mProfileManager.getHeadsetProfile();
171         if (hapProfile != null && hfpProfile != null && device == null) {
172             hfpProfile.setActiveDevice(null);
173             hapProfile.setActiveDevice(null);
174         } else if (hapProfile != null && device != null
175                 && hapProfile.getHiSyncId(device) != HI_SYNC_ID_INVALID) {
176             hapProfile.setActiveDevice(device);
177         } else if (hfpProfile != null) {
178             hfpProfile.setActiveDevice(device);
179         }
180     }
181 
182     @Override
findActiveDevice()183     public BluetoothDevice findActiveDevice() {
184         BluetoothDevice activeDevice = findActiveHearingAidDevice();
185         final HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
186 
187         if (activeDevice == null && headsetProfile != null) {
188             activeDevice = headsetProfile.getActiveDevice();
189         }
190         return activeDevice;
191     }
192 }
193