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