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