/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.telecom; import android.app.ActivityManagerNative; import android.content.Context; import android.content.pm.UserInfo; import android.media.AudioManager; import android.media.IAudioService; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.telecom.CallAudioState; import android.telecom.PhoneAccountHandle; import android.telephony.SubscriptionManager; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import java.util.Objects; /** * This class manages audio modes, streams and other properties. */ final class CallAudioManager extends CallsManagerListenerBase implements WiredHeadsetManager.Listener, DockManager.Listener { private static final int STREAM_NONE = -1; private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE"; private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM"; private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO"; private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF"; private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC"; private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION"; private static final String STREAM_DESCRIPTION_RING = "STREAM_RING"; private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM"; private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL"; private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID"; private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT"; private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL"; private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE"; private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL"; private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION"; private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0; private static final int MSG_AUDIO_MANAGER_TURN_ON_SPEAKER = 1; private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2; private static final int MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE = 3; private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4; private static final int MSG_AUDIO_MANAGER_SET_MODE = 5; private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) { private AudioManager mAudioManager; @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_AUDIO_MANAGER_INITIALIZE: { mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); break; } case MSG_AUDIO_MANAGER_TURN_ON_SPEAKER: { boolean on = (msg.arg1 != 0); // Wired headset and earpiece work the same way if (mAudioManager.isSpeakerphoneOn() != on) { Log.i(this, "turning speaker phone %s", on); mAudioManager.setSpeakerphoneOn(on); } break; } case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: { mAudioManager.abandonAudioFocusForCall(); break; } case MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE: { boolean mute = (msg.arg1 != 0); if (mute != mAudioManager.isMicrophoneMute()) { IAudioService audio = getAudioService(); Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", mute, audio == null); if (audio != null) { try { // We use the audio service directly here so that we can specify // the current user. Telecom runs in the system_server process which // may run as a separate user from the foreground user. If we // used AudioManager directly, we would change mute for the system's // user and not the current foreground, which we want to avoid. audio.setMicrophoneMute( mute, mContext.getOpPackageName(), getCurrentUserId()); } catch (RemoteException e) { Log.e(this, e, "Remote exception while toggling mute."); } // TODO: Check microphone state after attempting to set to ensure that // our state corroborates AudioManager's state. } } break; } case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: { int stream = msg.arg1; mAudioManager.requestAudioFocusForCall( stream, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); break; } case MSG_AUDIO_MANAGER_SET_MODE: { int newMode = msg.arg1; int oldMode = mAudioManager.getMode(); Call call = mCallsManager.getForegroundCall(); boolean setMsimAudioParams = SystemProperties .getBoolean("ro.multisim.set_audio_params", false); Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode), modeToString(newMode)); if (oldMode != newMode) { if (oldMode == AudioManager.MODE_IN_CALL && newMode == AudioManager.MODE_RINGTONE) { Log.i(this, "Transition from IN_CALL -> RINGTONE." + " Resetting to NORMAL first."); mAudioManager.setMode(AudioManager.MODE_NORMAL); } if (call != null && setMsimAudioParams && newMode == AudioManager.MODE_IN_CALL) { int phoneId = getPhoneId(call); Log.d(this, "setAudioParameters phoneId=" + phoneId); if (phoneId == 0) { mAudioManager.setParameters("phone_type=cp1"); } else if (phoneId == 1) { mAudioManager.setParameters("phone_type=cp2"); } } mAudioManager.setMode(newMode); synchronized (mLock) { mMostRecentlyUsedMode = newMode; } } break; } default: break; } } }; private final Context mContext; private final TelecomSystem.SyncRoot mLock; private final StatusBarNotifier mStatusBarNotifier; private final BluetoothManager mBluetoothManager; private final WiredHeadsetManager mWiredHeadsetManager; private final DockManager mDockManager; private final CallsManager mCallsManager; private CallAudioState mCallAudioState; private int mAudioFocusStreamType; private boolean mIsRinging; private boolean mIsTonePlaying; private boolean mWasSpeakerOn; private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL; private Call mCallToSpeedUpMTAudio = null; CallAudioManager( Context context, TelecomSystem.SyncRoot lock, StatusBarNotifier statusBarNotifier, WiredHeadsetManager wiredHeadsetManager, DockManager dockManager, CallsManager callsManager) { mContext = context; mLock = lock; mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE, 0, 0).sendToTarget(); mStatusBarNotifier = statusBarNotifier; mBluetoothManager = new BluetoothManager(context, this); mWiredHeadsetManager = wiredHeadsetManager; mCallsManager = callsManager; mWiredHeadsetManager.addListener(this); mDockManager = dockManager; mDockManager.addListener(this); saveAudioState(getInitialAudioState(null)); mAudioFocusStreamType = STREAM_NONE; } CallAudioState getCallAudioState() { return mCallAudioState; } @Override public void onCallAdded(Call call) { Log.v(this, "onCallAdded"); onCallUpdated(call); if (hasFocus() && getForegroundCall() == call) { if (!call.isIncoming()) { // Unmute new outgoing call. setSystemAudioState(false, mCallAudioState.getRoute(), mCallAudioState.getSupportedRouteMask()); } } } @Override public void onCallRemoved(Call call) { Log.v(this, "onCallRemoved"); // If we didn't already have focus, there's nothing to do. if (hasFocus()) { if (mCallsManager.getCalls().isEmpty()) { Log.v(this, "all calls removed, resetting system audio to default state"); setInitialAudioState(null, false /* force */); mWasSpeakerOn = false; } updateAudioStreamAndMode(call); } } @Override public void onCallStateChanged(Call call, int oldState, int newState) { Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState); onCallUpdated(call); } @Override public void onIncomingCallAnswered(Call call) { Log.v(this, "onIncomingCallAnswered"); int route = mCallAudioState.getRoute(); // BT stack will connect audio upon receiving active call state. // We unmute the audio for the new incoming call. setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask()); if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { Log.v(this, "Speed up audio setup for IMS MT call."); mCallToSpeedUpMTAudio = call; updateAudioStreamAndMode(call); } } @Override public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { onCallUpdated(newForegroundCall); // Ensure that the foreground call knows about the latest audio state. updateAudioForForegroundCall(); } @Override public void onIsVoipAudioModeChanged(Call call) { updateAudioStreamAndMode(call); } /** * Updates the audio route when the headset plugged in state changes. For example, if audio is * being routed over speakerphone and a headset is plugged in then switch to wired headset. */ @Override public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { // This can happen even when there are no calls and we don't have focus. if (!hasFocus()) { return; } boolean isCurrentlyWiredHeadset = mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET; int newRoute = mCallAudioState.getRoute(); // start out with existing route if (newIsPluggedIn) { newRoute = CallAudioState.ROUTE_WIRED_HEADSET; } else if (isCurrentlyWiredHeadset) { Call call = getForegroundCall(); boolean hasLiveCall = call != null && call.isAlive(); if (hasLiveCall) { // In order of preference when a wireless headset is unplugged. if (mWasSpeakerOn) { newRoute = CallAudioState.ROUTE_SPEAKER; } else { newRoute = CallAudioState.ROUTE_EARPIECE; } // We don't automatically connect to bluetooth when user unplugs their wired headset // and they were previously using the wired. Wired and earpiece are effectively the // same choice in that they replace each other as an option when wired headsets // are plugged in and out. This means that keeping it earpiece is a bit more // consistent with the status quo. Bluetooth also has more danger associated with // choosing it in the wrong curcumstance because bluetooth devices can be // semi-public (like in a very-occupied car) where earpiece doesn't carry that risk. } } // We need to call this every time even if we do not change the route because the supported // routes changed either to include or not include WIRED_HEADSET. setSystemAudioState(mCallAudioState.isMuted(), newRoute, calculateSupportedRoutes()); } @Override public void onDockChanged(boolean isDocked) { // This can happen even when there are no calls and we don't have focus. if (!hasFocus()) { return; } if (isDocked) { // Device just docked, turn to speakerphone. Only do so if the route is currently // earpiece so that we dont switch out of a BT headset or a wired headset. if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE) { setAudioRoute(CallAudioState.ROUTE_SPEAKER); } } else { // Device just undocked, remove from speakerphone if possible. if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE); } } } void toggleMute() { mute(!mCallAudioState.isMuted()); } void mute(boolean shouldMute) { if (!hasFocus()) { return; } Log.v(this, "mute, shouldMute: %b", shouldMute); // Don't mute if there are any emergency calls. if (mCallsManager.hasEmergencyCall()) { shouldMute = false; Log.v(this, "ignoring mute for emergency call"); } if (mCallAudioState.isMuted() != shouldMute) { // We user CallsManager's foreground call so that we dont ignore ringing calls // for logging purposes Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE, shouldMute ? "on" : "off"); setSystemAudioState(shouldMute, mCallAudioState.getRoute(), mCallAudioState.getSupportedRouteMask()); } } /** * Changed the audio route, for example from earpiece to speaker phone. * * @param route The new audio route to use. See {@link CallAudioState}. */ void setAudioRoute(int route) { // This can happen even when there are no calls and we don't have focus. if (!hasFocus()) { return; } Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); // Change ROUTE_WIRED_OR_EARPIECE to a single entry. int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask()); // If route is unsupported, do nothing. if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) { Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute); return; } if (mCallAudioState.getRoute() != newRoute) { // Remember the new speaker state so it can be restored when the user plugs and unplugs // a headset. mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER; setSystemAudioState(mCallAudioState.isMuted(), newRoute, mCallAudioState.getSupportedRouteMask()); } } /** * Sets the audio stream and mode based on whether a call is ringing. * * @param call The call which changed ringing state. * @param isRinging {@code true} if the call is ringing, {@code false} otherwise. */ void setIsRinging(Call call, boolean isRinging) { if (mIsRinging != isRinging) { Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call); mIsRinging = isRinging; updateAudioStreamAndMode(call); } } /** * Sets the tone playing status. Some tones can play even when there are no live calls and this * status indicates that we should keep audio focus even for tones that play beyond the life of * calls. * * @param isPlayingNew The status to set. */ void setIsTonePlaying(boolean isPlayingNew) { if (mIsTonePlaying != isPlayingNew) { Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew); mIsTonePlaying = isPlayingNew; updateAudioStreamAndMode(); } } /** * Updates the audio routing according to the bluetooth state. */ void onBluetoothStateChange(BluetoothManager bluetoothManager) { // This can happen even when there are no calls and we don't have focus. if (!hasFocus()) { return; } int supportedRoutes = calculateSupportedRoutes(); int newRoute = mCallAudioState.getRoute(); if (bluetoothManager.isBluetoothAudioConnectedOrPending()) { newRoute = CallAudioState.ROUTE_BLUETOOTH; } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) { newRoute = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRoutes); // Do not switch to speaker when bluetooth disconnects. mWasSpeakerOn = false; } setSystemAudioState(mCallAudioState.isMuted(), newRoute, supportedRoutes); } boolean isBluetoothAudioOn() { return mBluetoothManager.isBluetoothAudioConnected(); } boolean isBluetoothDeviceAvailable() { return mBluetoothManager.isBluetoothAvailable(); } private void saveAudioState(CallAudioState callAudioState) { mCallAudioState = callAudioState; mStatusBarNotifier.notifyMute(mCallAudioState.isMuted()); mStatusBarNotifier.notifySpeakerphone(mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER); } private void onCallUpdated(Call call) { if (call != null) { if (call.getState() != CallState.DISCONNECTED) { updateAudioStreamAndMode(call); } if (call.getState() == CallState.ACTIVE && call == mCallToSpeedUpMTAudio) { mCallToSpeedUpMTAudio = null; } } } private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) { setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask); } private void setSystemAudioState( boolean force, boolean isMuted, int route, int supportedRouteMask) { if (!hasFocus()) { return; } CallAudioState oldAudioState = mCallAudioState; saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask)); if (!force && Objects.equals(oldAudioState, mCallAudioState)) { return; } Log.i(this, "setSystemAudioState: changing from %s to %s", oldAudioState, mCallAudioState); Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, CallAudioState.audioRouteToString(mCallAudioState.getRoute())); mAudioManagerHandler.obtainMessage( MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE, mCallAudioState.isMuted() ? 1 : 0, 0) .sendToTarget(); // Audio route. if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) { turnOnBluetooth(true); } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { turnOnBluetooth(false); turnOnSpeaker(true); } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE || mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) { turnOnBluetooth(false); turnOnSpeaker(false); } if (!oldAudioState.equals(mCallAudioState)) { mCallsManager.onCallAudioStateChanged(oldAudioState, mCallAudioState); updateAudioForForegroundCall(); } } private void turnOnSpeaker(boolean on) { mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_TURN_ON_SPEAKER, on ? 1 : 0, 0) .sendToTarget(); } private void turnOnBluetooth(boolean on) { if (mBluetoothManager.isBluetoothAvailable()) { boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending(); if (on != isAlreadyOn) { Log.i(this, "connecting bluetooth %s", on); if (on) { mBluetoothManager.connectBluetoothAudio(); } else { mBluetoothManager.disconnectBluetoothAudio(); } } } } private void updateAudioStreamAndMode() { updateAudioStreamAndMode(null /* call */); } private void updateAudioStreamAndMode(Call callToUpdate) { Log.i(this, "updateAudioStreamAndMode : mIsRinging: %b, mIsTonePlaying: %b, call: %s", mIsRinging, mIsTonePlaying, callToUpdate); boolean wasVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL; if (mIsRinging) { Log.i(this, "updateAudioStreamAndMode : ringing"); requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE); } else { Call foregroundCall = getForegroundCall(); Call waitingForAccountSelectionCall = mCallsManager .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT); Call call = mCallsManager.getForegroundCall(); if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) { Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio."); requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, AudioManager.MODE_IN_CALL); } else if (foregroundCall != null && !foregroundCall.isDisconnected() && waitingForAccountSelectionCall == null) { // In the case where there is a call that is waiting for account selection, // this will fall back to abandonAudioFocus() below, which temporarily exits // the in-call audio mode. This is to allow TalkBack to speak the "Call with" // dialog information at media volume as opposed to through the earpiece. // Once exiting the "Call with" dialog, the audio focus will return to an in-call // audio mode when this method (updateAudioStreamAndMode) is called again. int mode = foregroundCall.getIsVoipAudioMode() ? AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL; Log.v(this, "updateAudioStreamAndMode : foreground"); requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode); } else if (mIsTonePlaying) { // There is no call, however, we are still playing a tone, so keep focus. // Since there is no call from which to determine the mode, use the most // recently used mode instead. Log.v(this, "updateAudioStreamAndMode : tone playing"); requestAudioFocusAndSetMode( AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode); } else if (!hasRingingForegroundCall() && mCallsManager.hasOnlyDisconnectedCalls()) { Log.v(this, "updateAudioStreamAndMode : no ringing call"); // Request to set audio mode normal. Here confirm if any call exist. if (!hasAnyCalls()) { abandonAudioFocus(); } } else { // mIsRinging is false, but there is a foreground ringing call present. Don't // abandon audio focus immediately to prevent audio focus from getting lost between // the time it takes for the foreground call to transition from RINGING to ACTIVE/ // DISCONNECTED. When the call eventually transitions to the next state, audio // focus will be correctly abandoned by the if clause above. } } boolean isVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL; // If we transition from not a voice call to a voice call, we need to set an initial audio // state for the call. if (!wasVoiceCall && isVoiceCall) { setInitialAudioState(callToUpdate, true /* force */); } } private void requestAudioFocusAndSetMode(int stream, int mode) { Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s", streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream), modeToString(mode)); Preconditions.checkState(stream != STREAM_NONE); // Even if we already have focus, if the stream is different we update audio manager to give // it a hint about the purpose of our focus. if (mAudioFocusStreamType != stream) { Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s", streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream)); mAudioManagerHandler.obtainMessage( MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL, stream, 0) .sendToTarget(); } mAudioFocusStreamType = stream; setMode(mode); } private void abandonAudioFocus() { if (hasFocus()) { setMode(AudioManager.MODE_NORMAL); Log.v(this, "abandoning audio focus"); mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL, 0, 0) .sendToTarget(); mAudioFocusStreamType = STREAM_NONE; mCallToSpeedUpMTAudio = null; } } private int getPhoneId(Call call) { if (call.getTargetPhoneAccount() != null) { PhoneAccountHandle account = call.getTargetPhoneAccount(); try { int index = Integer.parseInt(account.getId()); int phoneId = SubscriptionManager.getPhoneId(index); if (SubscriptionManager.isValidPhoneId(phoneId)) { return phoneId; } } catch (NumberFormatException e) { Log.e(this, e, "Cannot get phoneId from ID value " + account.getId()); } } return -1; } /** * Sets the audio mode. * * @param newMode Mode constant from AudioManager.MODE_*. */ private void setMode(int newMode) { Preconditions.checkState(hasFocus()); mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE, newMode, 0).sendToTarget(); } private int selectWiredOrEarpiece(int route, int supportedRouteMask) { // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is // supported before calling setAudioRoute. if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) { route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask; if (route == 0) { Log.wtf(this, "One of wired headset or earpiece should always be valid."); // assume earpiece in this case. route = CallAudioState.ROUTE_EARPIECE; } } return route; } private int calculateSupportedRoutes() { int routeMask = CallAudioState.ROUTE_SPEAKER; if (mWiredHeadsetManager.isPluggedIn()) { routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; } else { routeMask |= CallAudioState.ROUTE_EARPIECE; } if (mBluetoothManager.isBluetoothAvailable()) { routeMask |= CallAudioState.ROUTE_BLUETOOTH; } return routeMask; } private CallAudioState getInitialAudioState(Call call) { int supportedRouteMask = calculateSupportedRoutes(); int route = selectWiredOrEarpiece( CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask); // We want the UI to indicate that "bluetooth is in use" in two slightly different cases: // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call. // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio // *will* be routed to a bluetooth headset once the call is answered. In this case, just // check if the headset is available. Note this only applies when we are dealing with // the first call. if (call != null && mBluetoothManager.isBluetoothAvailable()) { switch(call.getState()) { case CallState.ACTIVE: case CallState.ON_HOLD: case CallState.DIALING: case CallState.CONNECTING: case CallState.RINGING: route = CallAudioState.ROUTE_BLUETOOTH; break; default: break; } } return new CallAudioState(false, route, supportedRouteMask); } private void setInitialAudioState(Call call, boolean force) { CallAudioState audioState = getInitialAudioState(call); Log.i(this, "setInitialAudioState : audioState = %s, call = %s", audioState, call); setSystemAudioState( force, audioState.isMuted(), audioState.getRoute(), audioState.getSupportedRouteMask()); } private void updateAudioForForegroundCall() { Call call = mCallsManager.getForegroundCall(); if (call != null && call.getConnectionService() != null) { call.getConnectionService().onCallAudioStateChanged(call, mCallAudioState); } } /** * Returns the current foreground call in order to properly set the audio mode. */ private Call getForegroundCall() { Call call = mCallsManager.getForegroundCall(); // We ignore any foreground call that is in the ringing state because we deal with ringing // calls exclusively through the mIsRinging variable set by {@link Ringer}. if (call != null && call.getState() == CallState.RINGING) { return null; } return call; } private boolean hasRingingForegroundCall() { Call call = mCallsManager.getForegroundCall(); return call != null && call.getState() == CallState.RINGING; } private boolean hasFocus() { return mAudioFocusStreamType != STREAM_NONE; } private IAudioService getAudioService() { return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE)); } private int getCurrentUserId() { final long ident = Binder.clearCallingIdentity(); try { UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser(); return currentUser.id; } catch (RemoteException e) { // Activity manager not running, nothing we can do assume user 0. } finally { Binder.restoreCallingIdentity(ident); } return UserHandle.USER_OWNER; } private boolean hasAnyCalls() { return mCallsManager.hasAnyCalls(); } /** * Translates an {@link AudioManager} stream type to a human-readable string description. * * @param streamType The stream type. * @return Human readable description. */ private String streamTypeToString(int streamType) { switch (streamType) { case STREAM_NONE: return STREAM_DESCRIPTION_NONE; case AudioManager.STREAM_ALARM: return STREAM_DESCRIPTION_ALARM; case AudioManager.STREAM_BLUETOOTH_SCO: return STREAM_DESCRIPTION_BLUETOOTH_SCO; case AudioManager.STREAM_DTMF: return STREAM_DESCRIPTION_DTMF; case AudioManager.STREAM_MUSIC: return STREAM_DESCRIPTION_MUSIC; case AudioManager.STREAM_NOTIFICATION: return STREAM_DESCRIPTION_NOTIFICATION; case AudioManager.STREAM_RING: return STREAM_DESCRIPTION_RING; case AudioManager.STREAM_SYSTEM: return STREAM_DESCRIPTION_SYSTEM; case AudioManager.STREAM_VOICE_CALL: return STREAM_DESCRIPTION_VOICE_CALL; default: return "STEAM_OTHER_" + streamType; } } /** * Translates an {@link AudioManager} mode into a human readable string. * * @param mode The mode. * @return The string. */ private String modeToString(int mode) { switch (mode) { case AudioManager.MODE_INVALID: return MODE_DESCRIPTION_INVALID; case AudioManager.MODE_CURRENT: return MODE_DESCRIPTION_CURRENT; case AudioManager.MODE_NORMAL: return MODE_DESCRIPTION_NORMAL; case AudioManager.MODE_RINGTONE: return MODE_DESCRIPTION_RINGTONE; case AudioManager.MODE_IN_CALL: return MODE_DESCRIPTION_IN_CALL; case AudioManager.MODE_IN_COMMUNICATION: return MODE_DESCRIPTION_IN_COMMUNICATION; default: return "MODE_OTHER_" + mode; } } /** * Dumps the state of the {@link CallAudioManager}. * * @param pw The {@code IndentingPrintWriter} to write the state to. */ public void dump(IndentingPrintWriter pw) { pw.println("mAudioState: " + mCallAudioState); pw.println("mBluetoothManager:"); pw.increaseIndent(); mBluetoothManager.dump(pw); pw.decreaseIndent(); if (mWiredHeadsetManager != null) { pw.println("mWiredHeadsetManager:"); pw.increaseIndent(); mWiredHeadsetManager.dump(pw); pw.decreaseIndent(); } else { pw.println("mWiredHeadsetManager: null"); } pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType)); pw.println("mIsRinging: " + mIsRinging); pw.println("mIsTonePlaying: " + mIsTonePlaying); pw.println("mWasSpeakerOn: " + mWasSpeakerOn); pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode)); } }