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