Home | History | Annotate | Download | only in voicemail
      1 /*
      2  * Copyright (C) 2015 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.dialer.app.voicemail;
     18 
     19 import android.content.Context;
     20 import android.media.AudioDeviceInfo;
     21 import android.media.AudioManager;
     22 import android.media.AudioManager.OnAudioFocusChangeListener;
     23 import android.telecom.CallAudioState;
     24 import com.android.dialer.common.LogUtil;
     25 import java.util.concurrent.RejectedExecutionException;
     26 
     27 /** This class manages all audio changes for voicemail playback. */
     28 public final class VoicemailAudioManager
     29     implements OnAudioFocusChangeListener, WiredHeadsetManager.Listener {
     30 
     31   private static final String TAG = "VoicemailAudioManager";
     32 
     33   public static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
     34 
     35   private AudioManager audioManager;
     36   private VoicemailPlaybackPresenter voicemailPlaybackPresenter;
     37   private WiredHeadsetManager wiredHeadsetManager;
     38   private boolean wasSpeakerOn;
     39   private CallAudioState callAudioState;
     40   private boolean bluetoothScoEnabled;
     41 
     42   public VoicemailAudioManager(
     43       Context context, VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
     44     audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     45     this.voicemailPlaybackPresenter = voicemailPlaybackPresenter;
     46     wiredHeadsetManager = new WiredHeadsetManager(context);
     47     wiredHeadsetManager.setListener(this);
     48 
     49     callAudioState = getInitialAudioState();
     50     LogUtil.i(
     51         "VoicemailAudioManager.VoicemailAudioManager", "Initial audioState = " + callAudioState);
     52   }
     53 
     54   public void requestAudioFocus() {
     55     int result =
     56         audioManager.requestAudioFocus(
     57             this, PLAYBACK_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
     58     if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
     59       throw new RejectedExecutionException("Could not capture audio focus.");
     60     }
     61     updateBluetoothScoState(true);
     62   }
     63 
     64   public void abandonAudioFocus() {
     65     updateBluetoothScoState(false);
     66     audioManager.abandonAudioFocus(this);
     67   }
     68 
     69   @Override
     70   public void onAudioFocusChange(int focusChange) {
     71     LogUtil.d("VoicemailAudioManager.onAudioFocusChange", "focusChange=" + focusChange);
     72     voicemailPlaybackPresenter.onAudioFocusChange(focusChange == AudioManager.AUDIOFOCUS_GAIN);
     73   }
     74 
     75   @Override
     76   public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
     77     LogUtil.i(
     78         "VoicemailAudioManager.onWiredHeadsetPluggedInChanged",
     79         "wired headset was plugged in changed: " + oldIsPluggedIn + " -> " + newIsPluggedIn);
     80 
     81     if (oldIsPluggedIn == newIsPluggedIn) {
     82       return;
     83     }
     84 
     85     int newRoute = callAudioState.getRoute(); // start out with existing route
     86     if (newIsPluggedIn) {
     87       newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
     88     } else {
     89       voicemailPlaybackPresenter.pausePlayback();
     90       if (wasSpeakerOn) {
     91         newRoute = CallAudioState.ROUTE_SPEAKER;
     92       } else {
     93         newRoute = CallAudioState.ROUTE_EARPIECE;
     94       }
     95     }
     96 
     97     voicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioState.ROUTE_SPEAKER);
     98 
     99     // We need to call this every time even if we do not change the route because the supported
    100     // routes changed either to include or not include WIRED_HEADSET.
    101     setSystemAudioState(
    102         new CallAudioState(false /* muted */, newRoute, calculateSupportedRoutes()));
    103   }
    104 
    105   public void setSpeakerphoneOn(boolean on) {
    106     setAudioRoute(on ? CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_WIRED_OR_EARPIECE);
    107   }
    108 
    109   public boolean isWiredHeadsetPluggedIn() {
    110     return wiredHeadsetManager.isPluggedIn();
    111   }
    112 
    113   public void registerReceivers() {
    114     // Receivers is plural because we expect to add bluetooth support.
    115     wiredHeadsetManager.registerReceiver();
    116   }
    117 
    118   public void unregisterReceivers() {
    119     wiredHeadsetManager.unregisterReceiver();
    120   }
    121 
    122   /**
    123    * Bluetooth SCO (Synchronous Connection-Oriented) is the "phone" bluetooth audio. The system will
    124    * route to the bluetooth headset automatically if A2DP ("media") is available, but if the headset
    125    * only supports SCO then dialer must route it manually.
    126    */
    127   private void updateBluetoothScoState(boolean hasAudioFocus) {
    128     if (hasAudioFocus) {
    129       if (hasMediaAudioCapability()) {
    130         bluetoothScoEnabled = false;
    131       } else {
    132         bluetoothScoEnabled = true;
    133         LogUtil.i(
    134             "VoicemailAudioManager.updateBluetoothScoState",
    135             "bluetooth device doesn't support media, using SCO instead");
    136       }
    137     } else {
    138       bluetoothScoEnabled = false;
    139     }
    140     applyBluetoothScoState();
    141   }
    142 
    143   private void applyBluetoothScoState() {
    144     if (bluetoothScoEnabled) {
    145       audioManager.startBluetoothSco();
    146       // The doc for startBluetoothSco() states it could take seconds to establish the SCO
    147       // connection, so we should probably resume the playback after we've acquired SCO.
    148       // In practice the delay is unnoticeable so this is ignored for simplicity.
    149       audioManager.setBluetoothScoOn(true);
    150     } else {
    151       audioManager.setBluetoothScoOn(false);
    152       audioManager.stopBluetoothSco();
    153     }
    154   }
    155 
    156   private boolean hasMediaAudioCapability() {
    157     for (AudioDeviceInfo info : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
    158       if (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
    159         return true;
    160       }
    161     }
    162     return false;
    163   }
    164 
    165   /**
    166    * Change the audio route, for example from earpiece to speakerphone.
    167    *
    168    * @param route The new audio route to use. See {@link CallAudioState}.
    169    */
    170   void setAudioRoute(int route) {
    171     LogUtil.v(
    172         "VoicemailAudioManager.setAudioRoute",
    173         "route: " + CallAudioState.audioRouteToString(route));
    174 
    175     // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
    176     int newRoute = selectWiredOrEarpiece(route, callAudioState.getSupportedRouteMask());
    177 
    178     // If route is unsupported, do nothing.
    179     if ((callAudioState.getSupportedRouteMask() | newRoute) == 0) {
    180       LogUtil.w(
    181           "VoicemailAudioManager.setAudioRoute",
    182           "Asking to set to a route that is unsupported: " + newRoute);
    183       return;
    184     }
    185 
    186     // Remember the new speaker state so it can be restored when the user plugs and unplugs
    187     // a headset.
    188     wasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
    189     setSystemAudioState(
    190         new CallAudioState(false /* muted */, newRoute, callAudioState.getSupportedRouteMask()));
    191   }
    192 
    193   private CallAudioState getInitialAudioState() {
    194     int supportedRouteMask = calculateSupportedRoutes();
    195     int route = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
    196     return new CallAudioState(false /* muted */, route, supportedRouteMask);
    197   }
    198 
    199   private int calculateSupportedRoutes() {
    200     int routeMask = CallAudioState.ROUTE_SPEAKER;
    201     if (wiredHeadsetManager.isPluggedIn()) {
    202       routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
    203     } else {
    204       routeMask |= CallAudioState.ROUTE_EARPIECE;
    205     }
    206     return routeMask;
    207   }
    208 
    209   private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
    210     // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
    211     // ROUTE_WIRED_OR_EARPIECE so that callers don't have to make a call to check which is
    212     // supported before calling setAudioRoute.
    213     if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
    214       route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
    215       if (route == 0) {
    216         LogUtil.e(
    217             "VoicemailAudioManager.selectWiredOrEarpiece",
    218             "One of wired headset or earpiece should always be valid.");
    219         // assume earpiece in this case.
    220         route = CallAudioState.ROUTE_EARPIECE;
    221       }
    222     }
    223     return route;
    224   }
    225 
    226   private void setSystemAudioState(CallAudioState callAudioState) {
    227     CallAudioState oldAudioState = this.callAudioState;
    228     this.callAudioState = callAudioState;
    229 
    230     LogUtil.i(
    231         "VoicemailAudioManager.setSystemAudioState",
    232         "changing from " + oldAudioState + " to " + this.callAudioState);
    233 
    234     // Audio route.
    235     if (this.callAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
    236       turnOnSpeaker(true);
    237     } else if (this.callAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE
    238         || this.callAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) {
    239       // Just handle turning off the speaker, the system will handle switching between wired
    240       // headset and earpiece.
    241       turnOnSpeaker(false);
    242       // BluetoothSco is not handled by the system so it has to be reset.
    243       applyBluetoothScoState();
    244     }
    245   }
    246 
    247   private void turnOnSpeaker(boolean on) {
    248     if (audioManager.isSpeakerphoneOn() != on) {
    249       LogUtil.i("VoicemailAudioManager.turnOnSpeaker", "turning speaker phone on: " + on);
    250       audioManager.setSpeakerphoneOn(on);
    251     }
    252   }
    253 }
    254