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