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