Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 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.telecomm; |
| 18 | |
| 19 | import android.content.Context; |
| 20 | import android.media.AudioManager; |
| 21 | import android.telecomm.CallState; |
| 22 | |
Santos Cordon | 1ae2b85 | 2014-03-19 03:03:10 -0700 | [diff] [blame^] | 23 | import com.google.common.collect.Lists; |
| 24 | |
| 25 | import java.util.List; |
| 26 | |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 27 | /** |
| 28 | * This class manages audio modes, streams and other properties. |
| 29 | */ |
| 30 | final class CallAudioManager extends CallsManagerListenerBase { |
Santos Cordon | 1ae2b85 | 2014-03-19 03:03:10 -0700 | [diff] [blame^] | 31 | private AsyncRingtonePlayer mRinger = new AsyncRingtonePlayer(); |
| 32 | |
| 33 | private boolean mHasAudioFocus = false; |
| 34 | |
| 35 | /** |
| 36 | * Used to keep ordering of unanswered incoming calls. The existence of multiple call services |
| 37 | * means that there can easily exist multiple incoming calls and explicit ordering is useful for |
| 38 | * maintaining the proper state of the ringer. |
| 39 | */ |
| 40 | private final List<String> mUnansweredCallIds = Lists.newLinkedList(); |
| 41 | |
| 42 | /** |
| 43 | * Denotes when the ringer is disabled. This is useful in temporarily disabling the ringer when |
| 44 | * the a call is answered/rejected by the user, but the call hasn't actually moved out of the |
| 45 | * ringing state. |
| 46 | */ |
| 47 | private boolean mIsRingingDisabled = false; |
| 48 | |
| 49 | @Override |
| 50 | public void onCallAdded(Call call) { |
| 51 | if (call.getState() == CallState.RINGING) { |
| 52 | mUnansweredCallIds.add(call.getId()); |
| 53 | } |
| 54 | updateAudio(); |
| 55 | } |
| 56 | |
| 57 | @Override |
| 58 | public void onCallRemoved(Call call) { |
| 59 | removeFromUnansweredCallIds(call.getId()); |
| 60 | updateAudio(); |
| 61 | } |
| 62 | |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 63 | @Override |
| 64 | public void onCallStateChanged(Call call, CallState oldState, CallState newState) { |
Santos Cordon | 1ae2b85 | 2014-03-19 03:03:10 -0700 | [diff] [blame^] | 65 | if (oldState == CallState.RINGING) { |
| 66 | removeFromUnansweredCallIds(call.getId()); |
| 67 | } |
| 68 | |
| 69 | updateAudio(); |
| 70 | } |
| 71 | |
| 72 | @Override |
| 73 | public void onIncomingCallAnswered(Call call) { |
| 74 | mIsRingingDisabled = true; |
| 75 | updateAudio(); |
| 76 | } |
| 77 | |
| 78 | @Override |
| 79 | public void onIncomingCallRejected(Call call) { |
| 80 | mIsRingingDisabled = true; |
| 81 | updateAudio(); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Reads the current state of all calls from CallsManager and sets the appropriate audio modes |
| 86 | * as well as triggers the start/stop of the ringer. |
| 87 | */ |
| 88 | private void updateAudio() { |
| 89 | CallsManager callsManager = CallsManager.getInstance(); |
| 90 | |
| 91 | boolean hasRingingCall = !mIsRingingDisabled && !mUnansweredCallIds.isEmpty(); |
| 92 | boolean hasLiveCall = callsManager.hasCallWithState(CallState.ACTIVE, CallState.DIALING); |
| 93 | |
| 94 | int mode = hasRingingCall ? AudioManager.MODE_RINGTONE : |
| 95 | hasLiveCall ? AudioManager.MODE_IN_CALL : |
| 96 | AudioManager.MODE_NORMAL; |
| 97 | |
| 98 | boolean needsFocus = (mode != AudioManager.MODE_NORMAL); |
| 99 | |
| 100 | // Acquiring focus needs to be first, unlike releasing focus, which happens at the end. |
| 101 | if (needsFocus) { |
| 102 | acquireFocus(hasRingingCall); |
| 103 | setMode(mode); |
| 104 | } |
| 105 | |
| 106 | if (hasRingingCall) { |
| 107 | mRinger.play(); |
| 108 | } else { |
| 109 | mRinger.stop(); |
| 110 | } |
| 111 | |
| 112 | if (!needsFocus) { |
| 113 | setMode(AudioManager.MODE_NORMAL); |
| 114 | releaseFocus(); |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | |
Santos Cordon | 1ae2b85 | 2014-03-19 03:03:10 -0700 | [diff] [blame^] | 118 | /** |
| 119 | * Acquires audio focus. |
| 120 | * |
| 121 | * @param isForRinging True if this focus is for playing the ringer. |
| 122 | */ |
| 123 | private void acquireFocus(boolean isForRinging) { |
| 124 | if (!mHasAudioFocus) { |
| 125 | int stream = isForRinging ? AudioManager.STREAM_RING : AudioManager.STREAM_VOICE_CALL; |
| 126 | |
| 127 | AudioManager audioManager = getAudioManager(); |
| 128 | audioManager.requestAudioFocusForCall(stream, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); |
| 129 | audioManager.setMicrophoneMute(false); |
| 130 | audioManager.setSpeakerphoneOn(false); |
| 131 | mHasAudioFocus = true; |
| 132 | } |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 133 | } |
| 134 | |
Santos Cordon | 1ae2b85 | 2014-03-19 03:03:10 -0700 | [diff] [blame^] | 135 | /** |
| 136 | * Releases focus. |
| 137 | */ |
| 138 | void releaseFocus() { |
| 139 | if (mHasAudioFocus) { |
| 140 | AudioManager audioManager = getAudioManager(); |
| 141 | |
| 142 | // Reset speakerphone and mute in case they were changed by telecomm. |
| 143 | audioManager.setMicrophoneMute(false); |
| 144 | audioManager.setSpeakerphoneOn(false); |
| 145 | audioManager.abandonAudioFocusForCall(); |
| 146 | |
| 147 | mHasAudioFocus = false; |
| 148 | Log.v(this, "Focus released"); |
| 149 | } |
| 150 | |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Sets the audio mode. |
| 155 | * |
| 156 | * @param mode Mode constant from AudioManager.MODE_*. |
| 157 | */ |
| 158 | void setMode(int mode) { |
| 159 | if (mHasAudioFocus) { |
| 160 | AudioManager audioManager = getAudioManager(); |
| 161 | if (mode != audioManager.getMode()) { |
| 162 | Log.v(this, "Audio mode set to %d.", mode); |
| 163 | audioManager.setMode(mode); |
| 164 | Log.v(this, "Audio mode actually set to %d.", audioManager.getMode()); |
| 165 | } |
| 166 | } else { |
| 167 | Log.wtf(this, "Trying to set audio mode to %d without focus.", mode); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Removes the specified call from the list of unanswered incoming calls. |
| 173 | * |
| 174 | * @param callId The ID of the call. |
| 175 | */ |
| 176 | private void removeFromUnansweredCallIds(String callId) { |
| 177 | if (!mUnansweredCallIds.isEmpty()) { |
| 178 | // If the call is the top-most call, then no longer disable the ringer. |
| 179 | if (callId.equals(mUnansweredCallIds.get(0))) { |
| 180 | mIsRingingDisabled = false; |
| 181 | } |
| 182 | |
| 183 | mUnansweredCallIds.remove(callId); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Returns the system audio manager. |
| 189 | */ |
| 190 | private AudioManager getAudioManager() { |
| 191 | return (AudioManager) TelecommApp.getInstance().getSystemService(Context.AUDIO_SERVICE); |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 192 | } |
| 193 | } |