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