blob: 69d3a6a2f891fade84c7855fe24eba07e4d20aed [file] [log] [blame]
Sailesh Nepal6aca10a2014-03-24 16:11:02 -07001/*
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
17package com.android.telecomm;
18
19import android.content.Context;
John Spurlock44b18102014-07-18 19:10:16 -040020import android.media.AudioAttributes;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070021import android.media.AudioManager;
Evan Charlton198fde82014-04-07 10:53:11 -070022import android.os.SystemVibrator;
23import android.os.Vibrator;
24import android.provider.Settings;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070025import android.telecomm.CallState;
26
Santos Cordon5ba7f272014-05-28 13:59:49 -070027import java.util.LinkedList;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070028import java.util.List;
29
30/**
31 * Controls the ringtone player.
32 */
33final class Ringer extends CallsManagerListenerBase {
Evan Charlton198fde82014-04-07 10:53:11 -070034 private static final long[] VIBRATION_PATTERN = new long[] {
35 0, // No delay before starting
36 1000, // How long to vibrate
37 1000, // How long to wait before vibrating again
38 };
39
John Spurlock44b18102014-07-18 19:10:16 -040040 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
41 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
Jean-Michel Trivi29e52ae2014-07-20 13:23:45 -070042 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
John Spurlock44b18102014-07-18 19:10:16 -040043 .build();
44
Evan Charlton198fde82014-04-07 10:53:11 -070045 /** Indicate that we want the pattern to repeat at the step which turns on vibration. */
46 private static final int VIBRATION_PATTERN_REPEAT = 1;
47
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070048 private final AsyncRingtonePlayer mRingtonePlayer = new AsyncRingtonePlayer();
49
50 /**
51 * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
52 * calls and explicit ordering is useful for maintaining the proper state of the ringer.
53 */
Santos Cordon5ba7f272014-05-28 13:59:49 -070054 private final List<Call> mRingingCalls = new LinkedList<>();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070055
56 private final CallAudioManager mCallAudioManager;
Santos Cordon40f78c22014-04-07 02:11:42 -070057 private final CallsManager mCallsManager;
58 private final InCallTonePlayer.Factory mPlayerFactory;
59 private final Context mContext;
Evan Charlton198fde82014-04-07 10:53:11 -070060 private final Vibrator mVibrator;
61
Santos Cordon40f78c22014-04-07 02:11:42 -070062 private InCallTonePlayer mCallWaitingPlayer;
63
Evan Charlton198fde82014-04-07 10:53:11 -070064 /**
65 * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
66 */
67 private boolean mIsVibrating = false;
68
Santos Cordon40f78c22014-04-07 02:11:42 -070069 /** Initializes the Ringer. */
70 Ringer(
71 CallAudioManager callAudioManager,
72 CallsManager callsManager,
73 InCallTonePlayer.Factory playerFactory,
74 Context context) {
Evan Charlton198fde82014-04-07 10:53:11 -070075
Santos Cordon40f78c22014-04-07 02:11:42 -070076 mCallAudioManager = callAudioManager;
77 mCallsManager = callsManager;
78 mPlayerFactory = playerFactory;
79 mContext = context;
Evan Charlton198fde82014-04-07 10:53:11 -070080 // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
81 // vibrator object will be isolated from others.
82 mVibrator = new SystemVibrator(TelecommApp.getInstance());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070083 }
84
85 @Override
Santos Cordon5ba7f272014-05-28 13:59:49 -070086 public void onCallAdded(final Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070087 if (call.isIncoming() && call.getState() == CallState.RINGING) {
Santos Cordonb64c1502014-05-21 21:21:49 -070088 if (mRingingCalls.contains(call)) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070089 Log.wtf(this, "New ringing call is already in list of unanswered calls");
90 }
Santos Cordonb64c1502014-05-21 21:21:49 -070091 mRingingCalls.add(call);
Santos Cordon40f78c22014-04-07 02:11:42 -070092 updateRinging();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070093 }
94 }
95
96 @Override
97 public void onCallRemoved(Call call) {
Sailesh Nepale59bb192014-04-01 18:33:59 -070098 removeFromUnansweredCall(call);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070099 }
100
101 @Override
102 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
103 if (newState != CallState.RINGING) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700104 removeFromUnansweredCall(call);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700105 }
106 }
107
108 @Override
109 public void onIncomingCallAnswered(Call call) {
110 onRespondedToIncomingCall(call);
111 }
112
113 @Override
Ihab Awadff7493a2014-06-10 13:47:44 -0700114 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700115 onRespondedToIncomingCall(call);
116 }
117
Santos Cordon40f78c22014-04-07 02:11:42 -0700118 @Override
119 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700120 if (mRingingCalls.contains(oldForegroundCall) ||
121 mRingingCalls.contains(newForegroundCall)) {
Santos Cordon40f78c22014-04-07 02:11:42 -0700122 updateRinging();
123 }
124 }
125
Santos Cordonb64c1502014-05-21 21:21:49 -0700126 /**
127 * Silences the ringer for any actively ringing calls.
128 */
129 void silence() {
130 // Remove all calls from the "ringing" set and then update the ringer.
131 mRingingCalls.clear();
132 updateRinging();
133 }
134
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700135 private void onRespondedToIncomingCall(Call call) {
136 // Only stop the ringer if this call is the top-most incoming call.
Santos Cordon40f78c22014-04-07 02:11:42 -0700137 if (getTopMostUnansweredCall() == call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700138 stopRinging();
Santos Cordon40f78c22014-04-07 02:11:42 -0700139 stopCallWaiting();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700140 }
Santos Cordon40f78c22014-04-07 02:11:42 -0700141
Santos Cordonb64c1502014-05-21 21:21:49 -0700142 // We do not remove the call from mRingingCalls until the call state changes from RINGING
Santos Cordon40f78c22014-04-07 02:11:42 -0700143 // or the call is removed. see onCallStateChanged or onCallRemoved.
144 }
145
146 private Call getTopMostUnansweredCall() {
Santos Cordonb64c1502014-05-21 21:21:49 -0700147 return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700148 }
149
150 /**
151 * Removes the specified call from the list of unanswered incoming calls and updates the ringer
Santos Cordonb64c1502014-05-21 21:21:49 -0700152 * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
Sailesh Nepale59bb192014-04-01 18:33:59 -0700153 * present in the list of incoming calls.
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700154 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700155 private void removeFromUnansweredCall(Call call) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700156 mRingingCalls.remove(call);
Santos Cordon40f78c22014-04-07 02:11:42 -0700157 updateRinging();
158 }
159
160 private void updateRinging() {
Santos Cordonb64c1502014-05-21 21:21:49 -0700161 if (mRingingCalls.isEmpty()) {
Santos Cordon40f78c22014-04-07 02:11:42 -0700162 stopRinging();
163 stopCallWaiting();
164 } else {
165 startRingingOrCallWaiting();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700166 }
167 }
168
Santos Cordon40f78c22014-04-07 02:11:42 -0700169 private void startRingingOrCallWaiting() {
170 Call foregroundCall = mCallsManager.getForegroundCall();
171 Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
Evan Charlton198fde82014-04-07 10:53:11 -0700172
Santos Cordonb64c1502014-05-21 21:21:49 -0700173 if (mRingingCalls.contains(foregroundCall)) {
Santos Cordon40f78c22014-04-07 02:11:42 -0700174 // The foreground call is one of incoming calls so play the ringer out loud.
175 stopCallWaiting();
176
177 AudioManager audioManager =
178 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
179 if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
180 Log.v(this, "startRingingOrCallWaiting");
181 mCallAudioManager.setIsRinging(true);
182
Santos Cordonc7e85d42014-05-22 02:51:48 -0700183 // Only play ringtone if a bluetooth device is not available. When a BT device
184 // is available, then we send it a signal to do its own ringtone and we dont need
185 // to play the ringtone on the device.
186 if (!mCallAudioManager.isBluetoothDeviceAvailable()) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700187 // Because we wait until a contact info query to complete before processing a
188 // call (for the purposes of direct-to-voicemail), the information about custom
189 // ringtones should be available by the time this code executes. We can safely
190 // request the custom ringtone from the call and expect it to be current.
191 mRingtonePlayer.play(foregroundCall.getRingtone());
Santos Cordonc7e85d42014-05-22 02:51:48 -0700192 }
Santos Cordon40f78c22014-04-07 02:11:42 -0700193 } else {
194 Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
195 }
Evan Charlton2186f7f2014-04-10 12:00:37 -0700196
197 if (shouldVibrate(TelecommApp.getInstance()) && !mIsVibrating) {
198 mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
John Spurlock44b18102014-07-18 19:10:16 -0400199 VIBRATION_ATTRIBUTES);
Evan Charlton2186f7f2014-04-10 12:00:37 -0700200 mIsVibrating = true;
201 }
Santos Cordon40f78c22014-04-07 02:11:42 -0700202 } else {
203 Log.v(this, "Playing call-waiting tone.");
204
205 // All incoming calls are in background so play call waiting.
206 stopRinging();
207
208 if (mCallWaitingPlayer == null) {
209 mCallWaitingPlayer =
210 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
211 mCallWaitingPlayer.startTone();
212 }
Evan Charlton198fde82014-04-07 10:53:11 -0700213 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700214 }
215
216 private void stopRinging() {
217 Log.v(this, "stopRinging");
Santos Cordon40f78c22014-04-07 02:11:42 -0700218
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700219 mRingtonePlayer.stop();
Evan Charlton198fde82014-04-07 10:53:11 -0700220
221 if (mIsVibrating) {
222 mVibrator.cancel();
223 mIsVibrating = false;
224 }
Santos Cordon40f78c22014-04-07 02:11:42 -0700225
226 // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
227 // focus are voluntary so releasing focus too early is not detrimental.
228 mCallAudioManager.setIsRinging(false);
229 }
230
231 private void stopCallWaiting() {
232 Log.v(this, "stop call waiting.");
233 if (mCallWaitingPlayer != null) {
234 mCallWaitingPlayer.stopTone();
235 mCallWaitingPlayer = null;
236 }
Evan Charlton198fde82014-04-07 10:53:11 -0700237 }
238
239 private boolean shouldVibrate(Context context) {
240 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
241 int ringerMode = audioManager.getRingerMode();
242 if (getVibrateWhenRinging(context)) {
243 return ringerMode != AudioManager.RINGER_MODE_SILENT;
244 } else {
245 return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
246 }
247 }
248
249 private boolean getVibrateWhenRinging(Context context) {
250 if (!mVibrator.hasVibrator()) {
251 return false;
252 }
253 return Settings.System.getInt(context.getContentResolver(),
254 Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700255 }
256}