Home | History | Annotate | Download | only in a2dpsink
      1 /*
      2  * Copyright (C) 2014 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.bluetooth.a2dpsink;
     18 
     19 import android.bluetooth.BluetoothAudioConfig;
     20 import android.bluetooth.BluetoothAvrcpController;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.bluetooth.BluetoothProfile;
     23 import android.bluetooth.IBluetoothA2dpSink;
     24 import android.content.Intent;
     25 import android.provider.Settings;
     26 import android.util.Log;
     27 import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
     28 import com.android.bluetooth.btservice.ProfileService;
     29 import com.android.bluetooth.Utils;
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 import java.util.Map;
     33 
     34 /**
     35  * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
     36  * @hide
     37  */
     38 public class A2dpSinkService extends ProfileService {
     39     private static final boolean DBG = true;
     40     private static final String TAG = "A2dpSinkService";
     41 
     42     private A2dpSinkStateMachine mStateMachine;
     43     private static A2dpSinkService sA2dpSinkService;
     44 
     45     protected String getName() {
     46         return TAG;
     47     }
     48 
     49     protected IProfileServiceBinder initBinder() {
     50         return new BluetoothA2dpSinkBinder(this);
     51     }
     52 
     53     protected boolean start() {
     54         if (DBG) {
     55             Log.d(TAG, "start()");
     56         }
     57         // Start the media browser service.
     58         Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
     59         startService(startIntent);
     60         mStateMachine = A2dpSinkStateMachine.make(this, this);
     61         setA2dpSinkService(this);
     62         return true;
     63     }
     64 
     65     protected boolean stop() {
     66         if (DBG) {
     67             Log.d(TAG, "stop()");
     68         }
     69         mStateMachine.doQuit();
     70         Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
     71         stopService(stopIntent);
     72         return true;
     73     }
     74 
     75     protected boolean cleanup() {
     76         if (mStateMachine!= null) {
     77             mStateMachine.cleanup();
     78         }
     79         clearA2dpSinkService();
     80         return true;
     81     }
     82 
     83     //API Methods
     84 
     85     public static synchronized A2dpSinkService getA2dpSinkService(){
     86         if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) {
     87             if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService);
     88             return sA2dpSinkService;
     89         }
     90         if (DBG)  {
     91             if (sA2dpSinkService == null) {
     92                 Log.d(TAG, "getA2dpSinkService(): service is NULL");
     93             } else if (!(sA2dpSinkService.isAvailable())) {
     94                 Log.d(TAG,"getA2dpSinkService(): service is not available");
     95             }
     96         }
     97         return null;
     98     }
     99 
    100     private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
    101         if (instance != null && instance.isAvailable()) {
    102             if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService);
    103             sA2dpSinkService = instance;
    104         } else {
    105             if (DBG)  {
    106                 if (sA2dpSinkService == null) {
    107                     Log.d(TAG, "setA2dpSinkService(): service not available");
    108                 } else if (!sA2dpSinkService.isAvailable()) {
    109                     Log.d(TAG,"setA2dpSinkService(): service is cleaning up");
    110                 }
    111             }
    112         }
    113     }
    114 
    115     private static synchronized void clearA2dpSinkService() {
    116         sA2dpSinkService = null;
    117     }
    118 
    119     public boolean connect(BluetoothDevice device) {
    120         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    121                                        "Need BLUETOOTH ADMIN permission");
    122 
    123         int connectionState = mStateMachine.getConnectionState(device);
    124         if (connectionState == BluetoothProfile.STATE_CONNECTED ||
    125             connectionState == BluetoothProfile.STATE_CONNECTING) {
    126             return false;
    127         }
    128 
    129         mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
    130         return true;
    131     }
    132 
    133     boolean disconnect(BluetoothDevice device) {
    134         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    135                                        "Need BLUETOOTH ADMIN permission");
    136         int connectionState = mStateMachine.getConnectionState(device);
    137         if (connectionState != BluetoothProfile.STATE_CONNECTED &&
    138             connectionState != BluetoothProfile.STATE_CONNECTING) {
    139             return false;
    140         }
    141 
    142         mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
    143         return true;
    144     }
    145 
    146     public List<BluetoothDevice> getConnectedDevices() {
    147         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    148         return mStateMachine.getConnectedDevices();
    149     }
    150 
    151     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    152         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    153         return mStateMachine.getDevicesMatchingConnectionStates(states);
    154     }
    155 
    156     int getConnectionState(BluetoothDevice device) {
    157         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    158         return mStateMachine.getConnectionState(device);
    159     }
    160 
    161     public boolean setPriority(BluetoothDevice device, int priority) {
    162         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    163                                        "Need BLUETOOTH_ADMIN permission");
    164         Settings.Global.putInt(getContentResolver(),
    165             Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
    166             priority);
    167         if (DBG) {
    168             Log.d(TAG,"Saved priority " + device + " = " + priority);
    169         }
    170         return true;
    171     }
    172 
    173     public int getPriority(BluetoothDevice device) {
    174         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    175                                        "Need BLUETOOTH_ADMIN permission");
    176         int priority = Settings.Global.getInt(getContentResolver(),
    177             Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
    178             BluetoothProfile.PRIORITY_UNDEFINED);
    179         return priority;
    180     }
    181 
    182     /**
    183      * Called by AVRCP controller to provide information about the last user intent on CT.
    184      *
    185      * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
    186      * any incoming sound from the phone (and also retain focus for a few seconds before
    187      * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
    188      * component will take the focus away but also notify the stack to throw away incoming data.
    189      */
    190     public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
    191         if (mStateMachine != null) {
    192             if (keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY &&
    193                 keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
    194                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
    195             } else if ((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE ||
    196                        keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP) &&
    197                        keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
    198                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
    199             }
    200         }
    201     }
    202 
    203     /**
    204      * Called by AVRCP controller to provide information about the last user intent on TG.
    205      *
    206      * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
    207      * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
    208      * stopping playback.
    209      */
    210     public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
    211         if (mStateMachine != null) {
    212             if (!isPlaying) {
    213                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
    214             } else {
    215                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
    216             }
    217         }
    218     }
    219 
    220     synchronized boolean isA2dpPlaying(BluetoothDevice device) {
    221         enforceCallingOrSelfPermission(BLUETOOTH_PERM,
    222                                        "Need BLUETOOTH permission");
    223         if (DBG) {
    224             Log.d(TAG, "isA2dpPlaying(" + device + ")");
    225         }
    226         return mStateMachine.isPlaying(device);
    227     }
    228 
    229     BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
    230         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    231         return mStateMachine.getAudioConfig(device);
    232     }
    233 
    234     //Binder object: Must be static class or memory leak may occur
    235     private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
    236         implements IProfileServiceBinder {
    237         private A2dpSinkService mService;
    238 
    239         private A2dpSinkService getService() {
    240             if (!Utils.checkCaller()) {
    241                 Log.w(TAG,"A2dp call not allowed for non-active user");
    242                 return null;
    243             }
    244 
    245             if (mService != null && mService.isAvailable()) {
    246                 return mService;
    247             }
    248             return null;
    249         }
    250 
    251         BluetoothA2dpSinkBinder(A2dpSinkService svc) {
    252             mService = svc;
    253         }
    254 
    255         public boolean cleanup()  {
    256             mService = null;
    257             return true;
    258         }
    259 
    260         public boolean connect(BluetoothDevice device) {
    261             A2dpSinkService service = getService();
    262             if (service == null) return false;
    263             return service.connect(device);
    264         }
    265 
    266         public boolean disconnect(BluetoothDevice device) {
    267             A2dpSinkService service = getService();
    268             if (service == null) return false;
    269             return service.disconnect(device);
    270         }
    271 
    272         public List<BluetoothDevice> getConnectedDevices() {
    273             A2dpSinkService service = getService();
    274             if (service == null) return new ArrayList<BluetoothDevice>(0);
    275             return service.getConnectedDevices();
    276         }
    277 
    278         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    279             A2dpSinkService service = getService();
    280             if (service == null) return new ArrayList<BluetoothDevice>(0);
    281             return service.getDevicesMatchingConnectionStates(states);
    282         }
    283 
    284         public int getConnectionState(BluetoothDevice device) {
    285             A2dpSinkService service = getService();
    286             if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
    287             return service.getConnectionState(device);
    288         }
    289 
    290         public boolean isA2dpPlaying(BluetoothDevice device) {
    291             A2dpSinkService service = getService();
    292             if (service == null) return false;
    293             return service.isA2dpPlaying(device);
    294         }
    295 
    296         public boolean setPriority(BluetoothDevice device, int priority) {
    297             A2dpSinkService service = getService();
    298             if (service == null) return false;
    299             return service.setPriority(device, priority);
    300         }
    301 
    302         public int getPriority(BluetoothDevice device) {
    303             A2dpSinkService service = getService();
    304             if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
    305             return service.getPriority(device);
    306         }
    307 
    308         public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
    309             A2dpSinkService service = getService();
    310             if (service == null) return null;
    311             return service.getAudioConfig(device);
    312         }
    313     };
    314 
    315     @Override
    316     public void dump(StringBuilder sb) {
    317         super.dump(sb);
    318         if (mStateMachine != null) {
    319             mStateMachine.dump(sb);
    320         }
    321     }
    322 }
    323