• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.settingslib.bluetooth;
18 
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothClass;
22 import android.bluetooth.BluetoothCodecConfig;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothProfile;
25 import android.bluetooth.BluetoothUuid;
26 import android.content.Context;
27 import android.os.ParcelUuid;
28 import android.util.Log;
29 
30 import com.android.settingslib.R;
31 
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 
36 public class A2dpProfile implements LocalBluetoothProfile {
37     private static final String TAG = "A2dpProfile";
38 
39     private Context mContext;
40 
41     private BluetoothA2dp mService;
42     private boolean mIsProfileReady;
43 
44     private final CachedBluetoothDeviceManager mDeviceManager;
45 
46     static final ParcelUuid[] SINK_UUIDS = {
47         BluetoothUuid.AudioSink,
48         BluetoothUuid.AdvAudioDist,
49     };
50 
51     static final String NAME = "A2DP";
52     private final LocalBluetoothProfileManager mProfileManager;
53 
54     // Order of this profile in device profiles list
55     private static final int ORDINAL = 1;
56 
57     // These callbacks run on the main thread.
58     private final class A2dpServiceListener
59             implements BluetoothProfile.ServiceListener {
60 
onServiceConnected(int profile, BluetoothProfile proxy)61         public void onServiceConnected(int profile, BluetoothProfile proxy) {
62             mService = (BluetoothA2dp) proxy;
63             // We just bound to the service, so refresh the UI for any connected A2DP devices.
64             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
65             while (!deviceList.isEmpty()) {
66                 BluetoothDevice nextDevice = deviceList.remove(0);
67                 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
68                 // we may add a new device here, but generally this should not happen
69                 if (device == null) {
70                     Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
71                     device = mDeviceManager.addDevice(nextDevice);
72                 }
73                 device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
74                 device.refresh();
75             }
76             mIsProfileReady=true;
77             mProfileManager.callServiceConnectedListeners();
78         }
79 
onServiceDisconnected(int profile)80         public void onServiceDisconnected(int profile) {
81             mIsProfileReady=false;
82         }
83     }
84 
isProfileReady()85     public boolean isProfileReady() {
86         return mIsProfileReady;
87     }
88 
89     @Override
getProfileId()90     public int getProfileId() {
91         return BluetoothProfile.A2DP;
92     }
93 
A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)94     A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager,
95             LocalBluetoothProfileManager profileManager) {
96         mContext = context;
97         mDeviceManager = deviceManager;
98         mProfileManager = profileManager;
99         BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpServiceListener(),
100                 BluetoothProfile.A2DP);
101     }
102 
accessProfileEnabled()103     public boolean accessProfileEnabled() {
104         return true;
105     }
106 
isAutoConnectable()107     public boolean isAutoConnectable() {
108         return true;
109     }
110 
111     /**
112      * Get A2dp devices matching connection states{
113      * @code BluetoothProfile.STATE_CONNECTED,
114      * @code BluetoothProfile.STATE_CONNECTING,
115      * @code BluetoothProfile.STATE_DISCONNECTING}
116      *
117      * @return Matching device list
118      */
getConnectedDevices()119     public List<BluetoothDevice> getConnectedDevices() {
120         return getDevicesByStates(new int[] {
121                 BluetoothProfile.STATE_CONNECTED,
122                 BluetoothProfile.STATE_CONNECTING,
123                 BluetoothProfile.STATE_DISCONNECTING});
124     }
125 
126     /**
127      * Get A2dp devices matching connection states{
128      * @code BluetoothProfile.STATE_DISCONNECTED,
129      * @code BluetoothProfile.STATE_CONNECTED,
130      * @code BluetoothProfile.STATE_CONNECTING,
131      * @code BluetoothProfile.STATE_DISCONNECTING}
132      *
133      * @return Matching device list
134      */
getConnectableDevices()135     public List<BluetoothDevice> getConnectableDevices() {
136         return getDevicesByStates(new int[] {
137                 BluetoothProfile.STATE_DISCONNECTED,
138                 BluetoothProfile.STATE_CONNECTED,
139                 BluetoothProfile.STATE_CONNECTING,
140                 BluetoothProfile.STATE_DISCONNECTING});
141     }
142 
getDevicesByStates(int[] states)143     private List<BluetoothDevice> getDevicesByStates(int[] states) {
144         if (mService == null) {
145             return new ArrayList<BluetoothDevice>(0);
146         }
147         return mService.getDevicesMatchingConnectionStates(states);
148     }
149 
connect(BluetoothDevice device)150     public boolean connect(BluetoothDevice device) {
151         if (mService == null) return false;
152         return mService.connect(device);
153     }
154 
disconnect(BluetoothDevice device)155     public boolean disconnect(BluetoothDevice device) {
156         if (mService == null) return false;
157         // Downgrade priority as user is disconnecting the headset.
158         if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
159             mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
160         }
161         return mService.disconnect(device);
162     }
163 
getConnectionStatus(BluetoothDevice device)164     public int getConnectionStatus(BluetoothDevice device) {
165         if (mService == null) {
166             return BluetoothProfile.STATE_DISCONNECTED;
167         }
168         return mService.getConnectionState(device);
169     }
170 
setActiveDevice(BluetoothDevice device)171     public boolean setActiveDevice(BluetoothDevice device) {
172         if (mService == null) return false;
173         return mService.setActiveDevice(device);
174     }
175 
getActiveDevice()176     public BluetoothDevice getActiveDevice() {
177         if (mService == null) return null;
178         return mService.getActiveDevice();
179     }
180 
isPreferred(BluetoothDevice device)181     public boolean isPreferred(BluetoothDevice device) {
182         if (mService == null) return false;
183         return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
184     }
185 
getPreferred(BluetoothDevice device)186     public int getPreferred(BluetoothDevice device) {
187         if (mService == null) return BluetoothProfile.PRIORITY_OFF;
188         return mService.getPriority(device);
189     }
190 
setPreferred(BluetoothDevice device, boolean preferred)191     public void setPreferred(BluetoothDevice device, boolean preferred) {
192         if (mService == null) return;
193         if (preferred) {
194             if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
195                 mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
196             }
197         } else {
198             mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
199         }
200     }
isA2dpPlaying()201     boolean isA2dpPlaying() {
202         if (mService == null) return false;
203         List<BluetoothDevice> sinks = mService.getConnectedDevices();
204         for (BluetoothDevice device : sinks) {
205             if (mService.isA2dpPlaying(device)) {
206                 return true;
207             }
208         }
209         return false;
210     }
211 
supportsHighQualityAudio(BluetoothDevice device)212     public boolean supportsHighQualityAudio(BluetoothDevice device) {
213         int support = mService.supportsOptionalCodecs(device);
214         return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
215     }
216 
isHighQualityAudioEnabled(BluetoothDevice device)217     public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
218         int enabled = mService.getOptionalCodecsEnabled(device);
219         if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
220             return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
221         } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
222                 supportsHighQualityAudio(device)) {
223             // Since we don't have a stored preference and the device isn't connected, just return
224             // true since the default behavior when the device gets connected in the future would be
225             // to have optional codecs enabled.
226             return true;
227         }
228         BluetoothCodecConfig codecConfig = null;
229         if (mService.getCodecStatus(device) != null) {
230             codecConfig = mService.getCodecStatus(device).getCodecConfig();
231         }
232         if (codecConfig != null)  {
233             return !codecConfig.isMandatoryCodec();
234         } else {
235             return false;
236         }
237     }
238 
setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled)239     public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
240         int prefValue = enabled
241                 ? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED
242                 : BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
243         mService.setOptionalCodecsEnabled(device, prefValue);
244         if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
245             return;
246         }
247         if (enabled) {
248             mService.enableOptionalCodecs(device);
249         } else {
250             mService.disableOptionalCodecs(device);
251         }
252     }
253 
getHighQualityAudioOptionLabel(BluetoothDevice device)254     public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
255         int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
256         if (!supportsHighQualityAudio(device)
257                 || getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
258             return mContext.getString(unknownCodecId);
259         }
260         // We want to get the highest priority codec, since that's the one that will be used with
261         // this device, and see if it is high-quality (ie non-mandatory).
262         BluetoothCodecConfig[] selectable = null;
263         if (mService.getCodecStatus(device) != null) {
264             selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities();
265             // To get the highest priority, we sort in reverse.
266             Arrays.sort(selectable,
267                     (a, b) -> {
268                         return b.getCodecPriority() - a.getCodecPriority();
269                     });
270         }
271 
272         final BluetoothCodecConfig codecConfig = (selectable == null || selectable.length < 1)
273                 ? null : selectable[0];
274         final int codecType = (codecConfig == null || codecConfig.isMandatoryCodec())
275                 ? BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID : codecConfig.getCodecType();
276 
277         int index = -1;
278         switch (codecType) {
279            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
280                index = 1;
281                break;
282            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
283                index = 2;
284                break;
285            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
286                index = 3;
287                break;
288            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
289                index = 4;
290                break;
291            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
292                index = 5;
293                break;
294            }
295 
296         if (index < 0) {
297             return mContext.getString(unknownCodecId);
298         }
299         return mContext.getString(R.string.bluetooth_profile_a2dp_high_quality,
300                 mContext.getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles)[index]);
301     }
302 
toString()303     public String toString() {
304         return NAME;
305     }
306 
getOrdinal()307     public int getOrdinal() {
308         return ORDINAL;
309     }
310 
getNameResource(BluetoothDevice device)311     public int getNameResource(BluetoothDevice device) {
312         return R.string.bluetooth_profile_a2dp;
313     }
314 
getSummaryResourceForDevice(BluetoothDevice device)315     public int getSummaryResourceForDevice(BluetoothDevice device) {
316         int state = getConnectionStatus(device);
317         switch (state) {
318             case BluetoothProfile.STATE_DISCONNECTED:
319                 return R.string.bluetooth_a2dp_profile_summary_use_for;
320 
321             case BluetoothProfile.STATE_CONNECTED:
322                 return R.string.bluetooth_a2dp_profile_summary_connected;
323 
324             default:
325                 return BluetoothUtils.getConnectionStateSummary(state);
326         }
327     }
328 
getDrawableResource(BluetoothClass btClass)329     public int getDrawableResource(BluetoothClass btClass) {
330         return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
331     }
332 
finalize()333     protected void finalize() {
334         Log.d(TAG, "finalize()");
335         if (mService != null) {
336             try {
337                 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
338                                                                        mService);
339                 mService = null;
340             }catch (Throwable t) {
341                 Log.w(TAG, "Error cleaning up A2DP proxy", t);
342             }
343         }
344     }
345 }
346