Home | History | Annotate | Download | only in a2dpsink
      1 /*
      2  * Copyright (C) 2016 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.content.Context;
     20 import android.media.AudioAttributes;
     21 import android.media.AudioManager;
     22 import android.media.AudioManager.OnAudioFocusChangeListener;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.util.Log;
     26 
     27 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
     28 import com.android.bluetooth.R;
     29 
     30 /**
     31  * Bluetooth A2DP SINK Streaming Handler.
     32  *
     33  * This handler defines how the stack behaves once the A2DP connection is established and both
     34  * devices are ready for streaming. For simplification we assume that the connection can either
     35  * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
     36  * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
     37  *
     38  * Note: There are several different audio tracks that a connected phone may like to transmit over
     39  * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
     40  * track that is almost always accompanied with an AVRCP play/pause command.
     41  *
     42  * Streaming is initiated by either an explicit play command from user interaction or audio coming
     43  * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
     44  * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
     45  * a change to audio focus playback may be temporarily paused and then resumed when focus is
     46  * restored.
     47  */
     48 public class A2dpSinkStreamHandler extends Handler {
     49     private static final boolean DBG = false;
     50     private static final String TAG = "A2dpSinkStreamHandler";
     51 
     52     // Configuration Variables
     53     private static final int DEFAULT_DUCK_PERCENT = 25;
     54 
     55     // Incoming events.
     56     public static final int SRC_STR_START = 0; // Audio stream from remote device started
     57     public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped
     58     public static final int SNK_PLAY = 2; // Play command was generated from local device
     59     public static final int SNK_PAUSE = 3; // Pause command was generated from local device
     60     public static final int SRC_PLAY = 4; // Play command was generated from remote device
     61     public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
     62     public static final int DISCONNECT = 6; // Remote device was disconnected
     63     public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
     64 
     65     // Used to indicate focus lost
     66     private static final int STATE_FOCUS_LOST = 0;
     67     // Used to inform bluedroid that focus is granted
     68     private static final int STATE_FOCUS_GRANTED = 1;
     69 
     70     // Private variables.
     71     private A2dpSinkStateMachine mA2dpSinkSm;
     72     private Context mContext;
     73     private AudioManager mAudioManager;
     74     // Keep track if the remote device is providing audio
     75     private boolean mStreamAvailable = false;
     76     private boolean mSentPause = false;
     77     // Keep track of the relevant audio focus (None, Transient, Gain)
     78     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
     79 
     80     // Focus changes when we are currently holding focus.
     81     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
     82         public void onAudioFocusChange(int focusChange) {
     83             if (DBG) {
     84                 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
     85             }
     86             A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
     87                     .sendToTarget();
     88         }
     89     };
     90 
     91     public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
     92         mA2dpSinkSm = a2dpSinkSm;
     93         mContext = context;
     94         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     95     }
     96 
     97     @Override
     98     public void handleMessage(Message message) {
     99         if (DBG) {
    100             Log.d(TAG, " process message: " + message.what);
    101             Log.d(TAG, " audioFocus =  " + mAudioFocus);
    102         }
    103         switch (message.what) {
    104             case SRC_STR_START:
    105                 // Audio stream has started, stop it if we don't have focus.
    106                 mStreamAvailable = true;
    107                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
    108                     sendAvrcpPause();
    109                 } else {
    110                     startAvrcpUpdates();
    111                 }
    112                 break;
    113 
    114             case SRC_STR_STOP:
    115                 // Audio stream has stopped, maintain focus but stop avrcp updates.
    116                 mStreamAvailable = false;
    117                 stopAvrcpUpdates();
    118                 break;
    119 
    120             case SNK_PLAY:
    121                 // Local play command, gain focus and start avrcp updates.
    122                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
    123                     requestAudioFocus();
    124                 }
    125                 startAvrcpUpdates();
    126                 break;
    127 
    128             case SNK_PAUSE:
    129                 // Local pause command, maintain focus but stop avrcp updates.
    130                 stopAvrcpUpdates();
    131                 break;
    132 
    133             case SRC_PLAY:
    134                 // Remote play command, if we have audio focus update avrcp, otherwise send pause.
    135                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
    136                     sendAvrcpPause();
    137                 } else {
    138                     startAvrcpUpdates();
    139                 }
    140                 break;
    141 
    142             case SRC_PAUSE:
    143                 // Remote pause command, stop avrcp updates.
    144                 stopAvrcpUpdates();
    145                 break;
    146 
    147             case DISCONNECT:
    148                 // Remote device has disconnected, restore everything to default state.
    149                 sendAvrcpPause();
    150                 stopAvrcpUpdates();
    151                 abandonAudioFocus();
    152                 mSentPause = false;
    153                 break;
    154 
    155             case AUDIO_FOCUS_CHANGE:
    156                 // message.obj is the newly granted audio focus.
    157                 switch ((int) message.obj) {
    158                     case AudioManager.AUDIOFOCUS_GAIN:
    159                         // Begin playing audio, if we paused the remote, send a play now.
    160                         startAvrcpUpdates();
    161                         startFluorideStreaming();
    162                         if (mSentPause) {
    163                             sendAvrcpPlay();
    164                             mSentPause = false;
    165                         }
    166                         break;
    167 
    168                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    169                         // Make the volume duck.
    170                         int duckPercent = mContext.getResources().getInteger(
    171                                 R.integer.a2dp_sink_duck_percent);
    172                         if (duckPercent < 0 || duckPercent > 100) {
    173                             Log.e(TAG, "Invalid duck percent using default.");
    174                             duckPercent = DEFAULT_DUCK_PERCENT;
    175                         }
    176                         float duckRatio = (duckPercent / 100.0f);
    177                         if (DBG) {
    178                             Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio);
    179                         }
    180                         setFluorideAudioTrackGain(duckRatio);
    181                         break;
    182 
    183                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    184                         // Temporary loss of focus, if we are actively streaming pause the remote
    185                         // and make sure we resume playback when we regain focus.
    186                         if (mStreamAvailable) {
    187                             sendAvrcpPause();
    188                             mSentPause = true;
    189                         }
    190                         stopFluorideStreaming();
    191                         break;
    192 
    193                     case AudioManager.AUDIOFOCUS_LOSS:
    194                         // Permanent loss of focus probably due to another audio app, abandon focus
    195                         // and stop playback.
    196                         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
    197                         abandonAudioFocus();
    198                         sendAvrcpPause();
    199                         break;
    200                 }
    201                 break;
    202 
    203             default:
    204                 Log.w(TAG, "Received unexpected event: " + message.what);
    205         }
    206     }
    207 
    208     /**
    209      * Utility functions.
    210      */
    211     private int requestAudioFocus() {
    212         int focusRequestStatus = mAudioManager.requestAudioFocus(
    213                 mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    214         // If the request is granted begin streaming immediately and schedule an upgrade.
    215         if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    216             startAvrcpUpdates();
    217             startFluorideStreaming();
    218             mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
    219         }
    220         return focusRequestStatus;
    221     }
    222 
    223 
    224     private void abandonAudioFocus() {
    225         stopFluorideStreaming();
    226         mAudioManager.abandonAudioFocus(mAudioFocusListener);
    227         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
    228     }
    229 
    230     private void startFluorideStreaming() {
    231         mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
    232         mA2dpSinkSm.informAudioTrackGainNative(1.0f);
    233     }
    234 
    235     private void stopFluorideStreaming() {
    236         mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
    237     }
    238 
    239     private void setFluorideAudioTrackGain(float gain) {
    240         mA2dpSinkSm.informAudioTrackGainNative(gain);
    241     }
    242 
    243     private void startAvrcpUpdates() {
    244         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
    245         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
    246 
    247         if (DBG) {
    248             Log.d(TAG, "startAvrcpUpdates");
    249         }
    250         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
    251             avrcpService.startAvrcpUpdates();
    252         } else {
    253             Log.e(TAG, "startAvrcpUpdates failed because of connection.");
    254         }
    255     }
    256 
    257     private void stopAvrcpUpdates() {
    258         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
    259         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
    260 
    261         if (DBG) {
    262             Log.d(TAG, "stopAvrcpUpdates");
    263         }
    264         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
    265             avrcpService.stopAvrcpUpdates();
    266         } else {
    267             Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
    268         }
    269     }
    270 
    271     private void sendAvrcpPause() {
    272         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
    273         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
    274 
    275         if (DBG) {
    276             Log.d(TAG, "sendAvrcpPause");
    277         }
    278         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
    279             if (DBG) {
    280                 Log.d(TAG, "Pausing AVRCP.");
    281             }
    282             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
    283                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
    284                     AvrcpControllerService.KEY_STATE_PRESSED);
    285             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
    286                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
    287                     AvrcpControllerService.KEY_STATE_RELEASED);
    288         } else {
    289             Log.e(TAG, "Passthrough not sent, connection un-available.");
    290         }
    291     }
    292 
    293     private void sendAvrcpPlay() {
    294         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
    295         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
    296 
    297         if (DBG) {
    298             Log.d(TAG, "sendAvrcpPlay");
    299         }
    300         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
    301             if (DBG) {
    302                 Log.d(TAG, "Playing AVRCP.");
    303             }
    304             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
    305                     AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
    306                     AvrcpControllerService.KEY_STATE_PRESSED);
    307             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
    308                     AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
    309                     AvrcpControllerService.KEY_STATE_RELEASED);
    310         } else {
    311             Log.e(TAG, "Passthrough not sent, connection un-available.");
    312         }
    313     }
    314 }
    315