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