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.voicemail; 18 19 import android.content.Context; 20 import android.media.AudioManager; 21 import android.media.AudioManager.OnAudioFocusChangeListener; 22 import android.telecom.CallAudioState; 23 import android.util.Log; 24 25 import java.util.concurrent.RejectedExecutionException; 26 27 /** 28 * This class manages all audio changes for voicemail playback. 29 */ 30 final class VoicemailAudioManager implements OnAudioFocusChangeListener, 31 WiredHeadsetManager.Listener { 32 private static final String TAG = VoicemailAudioManager.class.getSimpleName(); 33 34 public static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL; 35 36 private AudioManager mAudioManager; 37 private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; 38 private WiredHeadsetManager mWiredHeadsetManager; 39 private boolean mWasSpeakerOn; 40 private CallAudioState mCallAudioState; 41 42 public VoicemailAudioManager(Context context, 43 VoicemailPlaybackPresenter voicemailPlaybackPresenter) { 44 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 45 mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; 46 mWiredHeadsetManager = new WiredHeadsetManager(context); 47 mWiredHeadsetManager.setListener(this); 48 49 mCallAudioState = getInitialAudioState(); 50 Log.i(TAG, "Initial audioState = " + mCallAudioState); 51 } 52 53 public void requestAudioFocus() { 54 int result = mAudioManager.requestAudioFocus( 55 this, 56 PLAYBACK_STREAM, 57 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 58 if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 59 throw new RejectedExecutionException("Could not capture audio focus."); 60 } 61 } 62 63 public void abandonAudioFocus() { 64 mAudioManager.abandonAudioFocus(this); 65 } 66 67 @Override 68 public void onAudioFocusChange(int focusChange) { 69 Log.d(TAG, "onAudioFocusChange: focusChange=" + focusChange); 70 mVoicemailPlaybackPresenter.onAudioFocusChange(focusChange == AudioManager.AUDIOFOCUS_GAIN); 71 } 72 73 @Override 74 public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { 75 Log.i(TAG, "wired headset was plugged in changed: " + oldIsPluggedIn 76 + " -> "+ newIsPluggedIn); 77 78 if (oldIsPluggedIn == newIsPluggedIn) { 79 return; 80 } 81 82 int newRoute = mCallAudioState.getRoute(); // start out with existing route 83 if (newIsPluggedIn) { 84 newRoute = CallAudioState.ROUTE_WIRED_HEADSET; 85 } else { 86 if (mWasSpeakerOn) { 87 newRoute = CallAudioState.ROUTE_SPEAKER; 88 } else { 89 newRoute = CallAudioState.ROUTE_EARPIECE; 90 } 91 } 92 93 mVoicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioState.ROUTE_SPEAKER); 94 95 // We need to call this every time even if we do not change the route because the supported 96 // routes changed either to include or not include WIRED_HEADSET. 97 setSystemAudioState( 98 new CallAudioState(false /* muted */, newRoute, calculateSupportedRoutes())); 99 } 100 101 public void setSpeakerphoneOn(boolean on) { 102 setAudioRoute(on ? CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_WIRED_OR_EARPIECE); 103 } 104 105 public boolean isWiredHeadsetPluggedIn() { 106 return mWiredHeadsetManager.isPluggedIn(); 107 } 108 109 public void registerReceivers() { 110 // Receivers is plural because we expect to add bluetooth support. 111 mWiredHeadsetManager.registerReceiver(); 112 } 113 114 public void unregisterReceivers() { 115 mWiredHeadsetManager.unregisterReceiver(); 116 } 117 118 /** 119 * Change the audio route, for example from earpiece to speakerphone. 120 * 121 * @param route The new audio route to use. See {@link CallAudioState}. 122 */ 123 void setAudioRoute(int route) { 124 Log.v(TAG, "setAudioRoute, route: " + CallAudioState.audioRouteToString(route)); 125 126 // Change ROUTE_WIRED_OR_EARPIECE to a single entry. 127 int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask()); 128 129 // If route is unsupported, do nothing. 130 if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) { 131 Log.w(TAG, "Asking to set to a route that is unsupported: " + newRoute); 132 return; 133 } 134 135 if (mCallAudioState.getRoute() != newRoute) { 136 // Remember the new speaker state so it can be restored when the user plugs and unplugs 137 // a headset. 138 mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER; 139 setSystemAudioState(new CallAudioState(false /* muted */, newRoute, 140 mCallAudioState.getSupportedRouteMask())); 141 } 142 } 143 144 private CallAudioState getInitialAudioState() { 145 int supportedRouteMask = calculateSupportedRoutes(); 146 int route = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, 147 supportedRouteMask); 148 return new CallAudioState(false /* muted */, route, supportedRouteMask); 149 } 150 151 private int calculateSupportedRoutes() { 152 int routeMask = CallAudioState.ROUTE_SPEAKER; 153 if (mWiredHeadsetManager.isPluggedIn()) { 154 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; 155 } else { 156 routeMask |= CallAudioState.ROUTE_EARPIECE; 157 } 158 return routeMask; 159 } 160 161 private int selectWiredOrEarpiece(int route, int supportedRouteMask) { 162 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of 163 // ROUTE_WIRED_OR_EARPIECE so that callers don't have to make a call to check which is 164 // supported before calling setAudioRoute. 165 if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) { 166 route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask; 167 if (route == 0) { 168 Log.wtf(TAG, "One of wired headset or earpiece should always be valid."); 169 // assume earpiece in this case. 170 route = CallAudioState.ROUTE_EARPIECE; 171 } 172 } 173 return route; 174 } 175 176 private void setSystemAudioState(CallAudioState callAudioState) { 177 CallAudioState oldAudioState = mCallAudioState; 178 mCallAudioState = callAudioState; 179 180 Log.i(TAG, "setSystemAudioState: changing from " + oldAudioState + " to " 181 + mCallAudioState); 182 183 // Audio route. 184 if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { 185 turnOnSpeaker(true); 186 } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE || 187 mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) { 188 // Just handle turning off the speaker, the system will handle switching between wired 189 // headset and earpiece. 190 turnOnSpeaker(false); 191 } 192 } 193 194 private void turnOnSpeaker(boolean on) { 195 if (mAudioManager.isSpeakerphoneOn() != on) { 196 Log.i(TAG, "turning speaker phone on: " + on); 197 mAudioManager.setSpeakerphoneOn(on); 198 } 199 } 200 } 201