Home | History | Annotate | Download | only in bluetooth
      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 
     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 
     80         public void onServiceDisconnected(int profile) {
     81             mIsProfileReady=false;
     82         }
     83     }
     84 
     85     public boolean isProfileReady() {
     86         return mIsProfileReady;
     87     }
     88 
     89     @Override
     90     public int getProfileId() {
     91         return BluetoothProfile.A2DP;
     92     }
     93 
     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 
    103     public boolean accessProfileEnabled() {
    104         return true;
    105     }
    106 
    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      */
    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      */
    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 
    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 
    150     public boolean connect(BluetoothDevice device) {
    151         if (mService == null) return false;
    152         return mService.connect(device);
    153     }
    154 
    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 
    164     public int getConnectionStatus(BluetoothDevice device) {
    165         if (mService == null) {
    166             return BluetoothProfile.STATE_DISCONNECTED;
    167         }
    168         return mService.getConnectionState(device);
    169     }
    170 
    171     public boolean setActiveDevice(BluetoothDevice device) {
    172         if (mService == null) return false;
    173         return mService.setActiveDevice(device);
    174     }
    175 
    176     public BluetoothDevice getActiveDevice() {
    177         if (mService == null) return null;
    178         return mService.getActiveDevice();
    179     }
    180 
    181     public boolean isPreferred(BluetoothDevice device) {
    182         if (mService == null) return false;
    183         return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
    184     }
    185 
    186     public int getPreferred(BluetoothDevice device) {
    187         if (mService == null) return BluetoothProfile.PRIORITY_OFF;
    188         return mService.getPriority(device);
    189     }
    190 
    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     }
    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 
    212     public boolean supportsHighQualityAudio(BluetoothDevice device) {
    213         int support = mService.supportsOptionalCodecs(device);
    214         return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
    215     }
    216 
    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 
    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 
    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 
    303     public String toString() {
    304         return NAME;
    305     }
    306 
    307     public int getOrdinal() {
    308         return ORDINAL;
    309     }
    310 
    311     public int getNameResource(BluetoothDevice device) {
    312         return R.string.bluetooth_profile_a2dp;
    313     }
    314 
    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 
    329     public int getDrawableResource(BluetoothClass btClass) {
    330         return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
    331     }
    332 
    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