1 /* 2 * Copyright (C) 2017 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.development; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothCodecConfig; 22 import android.bluetooth.BluetoothCodecStatus; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothManager; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.Context; 27 28 import androidx.annotation.VisibleForTesting; 29 import androidx.preference.ListPreference; 30 import androidx.preference.Preference; 31 import androidx.preference.PreferenceScreen; 32 33 import com.android.settings.core.PreferenceControllerMixin; 34 import com.android.settingslib.core.lifecycle.Lifecycle; 35 import com.android.settingslib.core.lifecycle.LifecycleObserver; 36 import com.android.settingslib.core.lifecycle.events.OnDestroy; 37 import com.android.settingslib.development.DeveloperOptionsPreferenceController; 38 39 import java.util.List; 40 41 public abstract class AbstractBluetoothA2dpPreferenceController extends 42 DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, 43 PreferenceControllerMixin, BluetoothServiceConnectionListener, LifecycleObserver, 44 OnDestroy { 45 46 @VisibleForTesting 47 static final int STREAMING_LABEL_ID = 48 com.android.settingslib.R.string.bluetooth_select_a2dp_codec_streaming_label; 49 50 protected final BluetoothA2dpConfigStore mBluetoothA2dpConfigStore; 51 protected BluetoothA2dp mBluetoothA2dp; 52 protected ListPreference mPreference; 53 private final String[] mListValues; 54 private final String[] mListSummaries; 55 56 @VisibleForTesting 57 BluetoothAdapter mBluetoothAdapter; 58 AbstractBluetoothA2dpPreferenceController(Context context, Lifecycle lifecycle, BluetoothA2dpConfigStore store)59 public AbstractBluetoothA2dpPreferenceController(Context context, Lifecycle lifecycle, 60 BluetoothA2dpConfigStore store) { 61 super(context); 62 63 mBluetoothA2dpConfigStore = store; 64 mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); 65 mListValues = getListValues(); 66 mListSummaries = getListSummaries(); 67 68 if (lifecycle != null) { 69 lifecycle.addObserver(this); 70 } 71 } 72 73 @Override displayPreference(PreferenceScreen screen)74 public void displayPreference(PreferenceScreen screen) { 75 super.displayPreference(screen); 76 77 mPreference = screen.findPreference(getPreferenceKey()); 78 79 // Set a default value because BluetoothCodecConfig is null initially. 80 mPreference.setValue(mListValues[getDefaultIndex()]); 81 mPreference.setSummary(mListSummaries[getDefaultIndex()]); 82 } 83 84 @Override onPreferenceChange(Preference preference, Object newValue)85 public boolean onPreferenceChange(Preference preference, Object newValue) { 86 if (mBluetoothA2dp == null) { 87 return false; 88 } 89 90 writeConfigurationValues(newValue); 91 92 final BluetoothCodecConfig codecConfig = mBluetoothA2dpConfigStore.createCodecConfig(); 93 synchronized (mBluetoothA2dpConfigStore) { 94 BluetoothDevice activeDevice = getA2dpActiveDevice(); 95 if (activeDevice == null) { 96 return false; 97 } 98 setCodecConfigPreference(activeDevice, codecConfig); 99 } 100 // Because the setting is not persisted into permanent storage, we cannot call update state 101 // here to update the preference. 102 // Instead, we just assume it was set and update the preference here. 103 final int index = mPreference.findIndexOfValue(newValue.toString()); 104 // We only want to append "Streaming" if not using default 105 if (index == getDefaultIndex()) { 106 mPreference.setSummary(mListSummaries[index]); 107 } else { 108 mPreference.setSummary( 109 mContext.getResources().getString(STREAMING_LABEL_ID, mListSummaries[index])); 110 } 111 return true; 112 } 113 114 @Override updateState(Preference preference)115 public void updateState(Preference preference) { 116 BluetoothDevice activeDevice = getA2dpActiveDevice(); 117 if (activeDevice == null || getCodecConfig(activeDevice) == null || mPreference == null) { 118 return; 119 } 120 121 BluetoothCodecConfig codecConfig; 122 synchronized (mBluetoothA2dpConfigStore) { 123 codecConfig = getCodecConfig(activeDevice); 124 } 125 126 final int index = getCurrentA2dpSettingIndex(codecConfig); 127 mPreference.setValue(mListValues[index]); 128 129 // We only want to append "Streaming" if not using default 130 if (index == getDefaultIndex()) { 131 mPreference.setSummary(mListSummaries[index]); 132 } else { 133 mPreference.setSummary( 134 mContext.getResources().getString(STREAMING_LABEL_ID, mListSummaries[index])); 135 } 136 137 writeConfigurationValues(mListValues[index]); 138 } 139 140 @Override onBluetoothServiceConnected(BluetoothA2dp bluetoothA2dp)141 public void onBluetoothServiceConnected(BluetoothA2dp bluetoothA2dp) { 142 mBluetoothA2dp = bluetoothA2dp; 143 updateState(mPreference); 144 } 145 146 @Override onBluetoothCodecUpdated()147 public void onBluetoothCodecUpdated() { 148 // intentional no-op 149 // We do not want to call update state here because the setting is not persisted in 150 // permanent storage. 151 } 152 153 @Override onBluetoothServiceDisconnected()154 public void onBluetoothServiceDisconnected() { 155 mBluetoothA2dp = null; 156 } 157 158 @Override onDestroy()159 public void onDestroy() { 160 mBluetoothA2dp = null; 161 } 162 163 /** 164 * @return an array of string values that correspond to the current {@link ListPreference}. 165 */ getListValues()166 protected abstract String[] getListValues(); 167 168 /** 169 * @return an array of string summaries that correspond to the current {@link ListPreference}. 170 */ getListSummaries()171 protected abstract String[] getListSummaries(); 172 173 /** 174 * Updates the new value to the {@link BluetoothA2dpConfigStore} and the {@link BluetoothA2dp}. 175 * 176 * @param newValue the new setting value 177 */ writeConfigurationValues(Object newValue)178 protected abstract void writeConfigurationValues(Object newValue); 179 180 /** 181 * @return the current selected index for the {@link ListPreference}. 182 */ getCurrentA2dpSettingIndex(BluetoothCodecConfig config)183 protected abstract int getCurrentA2dpSettingIndex(BluetoothCodecConfig config); 184 185 /** 186 * @return default setting index for the {@link ListPreference}. 187 */ getDefaultIndex()188 protected abstract int getDefaultIndex(); 189 190 @VisibleForTesting setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig config)191 void setCodecConfigPreference(BluetoothDevice device, 192 BluetoothCodecConfig config) { 193 BluetoothDevice bluetoothDevice = 194 (device != null) ? device : getA2dpActiveDevice(); 195 if (bluetoothDevice == null) { 196 return; 197 } 198 mBluetoothA2dp.setCodecConfigPreference(bluetoothDevice, config); 199 } 200 201 @VisibleForTesting getCodecConfig(BluetoothDevice device)202 BluetoothCodecConfig getCodecConfig(BluetoothDevice device) { 203 if (mBluetoothA2dp != null) { 204 BluetoothDevice bluetoothDevice = 205 (device != null) ? device : getA2dpActiveDevice(); 206 if (bluetoothDevice == null) { 207 return null; 208 } 209 BluetoothCodecStatus codecStatus = mBluetoothA2dp.getCodecStatus(bluetoothDevice); 210 if (codecStatus != null) { 211 return codecStatus.getCodecConfig(); 212 } 213 } 214 return null; 215 } 216 getA2dpActiveDevice()217 private BluetoothDevice getA2dpActiveDevice() { 218 if (mBluetoothAdapter == null) { 219 return null; 220 } 221 List<BluetoothDevice> activeDevices = 222 mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP); 223 return (activeDevices.size() > 0) ? activeDevices.get(0) : null; 224 } 225 } 226