blob: bca0d153719d254ba3e5b01294d2b31dfe6d2ddb [file] [log] [blame]
Santos Cordon4e9fffe2014-03-04 18:13:41 -08001/*
2 * Copyright 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
Santos Cordonbe322e92014-03-17 16:56:13 -070019import android.content.Context;
20import android.media.AudioManager;
21import android.media.MediaPlayer;
22import android.media.MediaPlayer.OnErrorListener;
23import android.media.MediaPlayer.OnPreparedListener;
Santos Cordon57a680f2014-03-07 14:48:11 -080024import android.media.RingtoneManager;
Santos Cordonbe322e92014-03-17 16:56:13 -070025import android.net.Uri;
26
27import com.google.common.base.Preconditions;
28
29import java.io.IOException;
Santos Cordon57a680f2014-03-07 14:48:11 -080030
Santos Cordon4e9fffe2014-03-04 18:13:41 -080031/**
32 * Controls ringing and vibration for incoming calls.
33 *
34 * TODO(santoscordon): Consider moving all ringing responsibility to InCall app as an implementation
35 * within InCallServiceBase.
36 */
Santos Cordonbe322e92014-03-17 16:56:13 -070037final class Ringer implements OnErrorListener, OnPreparedListener {
38 // States for the Ringer.
39 /** Actively playing the ringer. */
40 private static final int RINGING = 1;
Santos Cordon4e9fffe2014-03-04 18:13:41 -080041
Santos Cordonbe322e92014-03-17 16:56:13 -070042 /** Ringer currently stopped. */
43 private static final int STOPPED = 2;
Santos Cordon57a680f2014-03-07 14:48:11 -080044
Santos Cordonbe322e92014-03-17 16:56:13 -070045 /** {@link #mMediaPlayer} is preparing, expected to ring once prepared. */
46 private static final int PREPARING_WITH_RING = 3;
47
48 /** {@link #mMediaPlayer} is preparing, expected to stop once prepared. */
49 private static final int PREPARING_WITH_STOP = 4;
Santos Cordon57a680f2014-03-07 14:48:11 -080050
51 /**
Santos Cordonbe322e92014-03-17 16:56:13 -070052 * The current state of the ringer.
Santos Cordon57a680f2014-03-07 14:48:11 -080053 */
Santos Cordonbe322e92014-03-17 16:56:13 -070054 private int mState = STOPPED;
Santos Cordon57a680f2014-03-07 14:48:11 -080055
Santos Cordonbe322e92014-03-17 16:56:13 -070056 /** The active media player for the ringer. */
57 private MediaPlayer mMediaPlayer;
Santos Cordon57a680f2014-03-07 14:48:11 -080058
Santos Cordon4e9fffe2014-03-04 18:13:41 -080059 /**
60 * Starts the vibration, ringer, and/or call-waiting tone.
Santos Cordonbe322e92014-03-17 16:56:13 -070061 * TODO(santoscordon): vibration and call-waiting tone.
Santos Cordon4e9fffe2014-03-04 18:13:41 -080062 */
63 void startRinging() {
Santos Cordonbe322e92014-03-17 16:56:13 -070064 // Check if we are muted before playing the ringer.
65 if (getAudioManager().getStreamVolume(AudioManager.STREAM_RING) > 0) {
66 moveToState(RINGING);
67 } else {
68 Log.d(this, "Ringer play skipped due to muted volume.");
69 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -080070 }
71
72 /**
73 * Stops the vibration, ringer, and/or call-waiting tone.
74 */
75 void stopRinging() {
Santos Cordonbe322e92014-03-17 16:56:13 -070076 moveToState(STOPPED);
Santos Cordon57a680f2014-03-07 14:48:11 -080077 }
78
79 /**
Santos Cordonbe322e92014-03-17 16:56:13 -070080 * Handles asynchronous media player "prepared" response by playing the ringer if we are
81 * still expected to or uninitializing it if we've been asked to stop.
82 *
83 * {@inheritDoc}
Santos Cordon57a680f2014-03-07 14:48:11 -080084 */
Santos Cordonbe322e92014-03-17 16:56:13 -070085 @Override
86 public void onPrepared(MediaPlayer mediaPlayer) {
87 Preconditions.checkState(mMediaPlayer == null);
Santos Cordon57a680f2014-03-07 14:48:11 -080088
Santos Cordonbe322e92014-03-17 16:56:13 -070089 // See {@link #moveToState} for state transitions.
90 if (PREPARING_WITH_RING == mState) {
91 Log.i(this, "Playing the ringer.");
92 setRingerAudioMode();
93 mMediaPlayer = mediaPlayer;
94 mMediaPlayer.start();
95 setState(RINGING);
96 } else if (PREPARING_WITH_STOP == mState) {
97 mediaPlayer.release();
98 setState(STOPPED);
99 }
100 }
101
102 /** {@inheritDoc} */
103 @Override
104 public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
105 Log.i(this, "Mediaplayer failed to initialize. What: %d, extra: %d.", what, extra);
106 resetMediaPlayer();
107 setState(STOPPED);
108 return true;
109 }
110
111 /**
112 * Transitions the state of the ringer. State machine below. Any missing arrows imply that the
113 * state remains the same (e.g., (r) on RING state keeps it at RING state).
114 *
115 * +----------------(s)----------------------------+
116 * | |
117 * +----------------(e)-------+ |
118 * | | |
119 * +-> STOPPED -(r)-> PREPARING_WITH_RING +-(p)-> RING
120 * ^ ^ |
121 * | | |
122 * (p,e) (r) |
123 * | | +-(s)-> PREPARING_WITH_STOP
124 * | | | |
125 * | +----------------------+ |
126 * +---------------------------------------------+
127 *
128 * STOPPED - Ringer completely stopped, like its initial state.
129 * PREPARING_TO_RING - Media player preparing asynchronously to start ringing.
130 * RINGING - The ringtone is currently playing.
131 * PREPARING_TO_STOP - Media player is still preparing, but we've already been asked to stop.
132 *
133 * (r) - {@link #startRinging}
134 * (s) - {@link #stopRinging}
135 * (p) - {@link #onPrepared}
136 * (e) - {@link #onError}
137 */
138 private void moveToState(int newState) {
139 // Only this method sets PREPARING_* states.
140 Preconditions.checkState(newState == RINGING || newState == STOPPED);
141
142 if (newState == mState) {
143 return;
144 }
145
146 if (RINGING == newState) {
147 if (STOPPED == mState) {
148 // If we are stopped, we need to preparing the media player and wait for it to
149 // start the ring. New state set by prepareForRinging.
150 if (prepareForRinging()) {
151 setState(PREPARING_WITH_RING);
Santos Cordon57a680f2014-03-07 14:48:11 -0800152 }
Santos Cordonbe322e92014-03-17 16:56:13 -0700153 } else if (PREPARING_WITH_STOP == mState) {
154 // We are currently preparing the media player, but expect it to put the ringer into
155 // stop once prepared...change that to ring.
156 setState(PREPARING_WITH_RING);
Santos Cordon57a680f2014-03-07 14:48:11 -0800157 }
Santos Cordonbe322e92014-03-17 16:56:13 -0700158 } else if (STOPPED == newState) {
159 if (RINGING == mState) {
160 // We are currently ringing, so just stop it.
161 stopPlayingRinger();
162 setState(STOPPED);
163 } else if (PREPARING_WITH_RING == mState) {
164 // We are preparing the media player, make sure that when it is finished, it moves
165 // to STOPPED instead of ringing.
166 setState(PREPARING_WITH_STOP);
167 }
Santos Cordon57a680f2014-03-07 14:48:11 -0800168 }
Santos Cordon57a680f2014-03-07 14:48:11 -0800169 }
170
171 /**
Santos Cordonbe322e92014-03-17 16:56:13 -0700172 * Sets the ringer state and checks the current thread.
Santos Cordon57a680f2014-03-07 14:48:11 -0800173 *
Santos Cordonbe322e92014-03-17 16:56:13 -0700174 * @param newState The new state to set.
Santos Cordon57a680f2014-03-07 14:48:11 -0800175 */
Santos Cordonbe322e92014-03-17 16:56:13 -0700176 private void setState(int newState) {
177 ThreadUtil.checkOnMainThread();
178 Log.v(this, "setState, %d -> %d", mState, newState);
179 mState = newState;
180 }
181
182 /**
183 * Starts media player's asynchronous prepare. Response returned in either {@link #onError} or
184 * {@link #onPrepared}.
185 *
186 * @return True if the prepare was successfully started.
187 */
188 private boolean prepareForRinging() {
189 Log.i(this, "Preparing the ringer.");
190
191 Uri ringtoneUri = getCurrentRingtoneUri();
192 if (ringtoneUri == null) {
193 Log.e(this, null, "Ringtone not set.");
194 return false;
Santos Cordon57a680f2014-03-07 14:48:11 -0800195 }
196
Santos Cordonbe322e92014-03-17 16:56:13 -0700197 MediaPlayer mediaPlayer = new MediaPlayer();
198 mediaPlayer.setOnErrorListener(this);
199 mediaPlayer.setOnPreparedListener(this);
200 mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
201
202 try {
203 mediaPlayer.setDataSource(TelecommApp.getInstance(), ringtoneUri);
204 mediaPlayer.prepareAsync();
205 return true;
206 } catch (IOException e) {
207 mediaPlayer.reset();
208 mediaPlayer.release();
209
210 Log.e(this, e, "Failed to initialize media player for ringer: %s.", ringtoneUri);
211 return false;
212 }
213 }
214
215 /**
216 * Stops and uninitializes the media player.
217 */
218 private void stopPlayingRinger() {
219 Preconditions.checkNotNull(mMediaPlayer);
220 Log.i(this, "Stopping the ringer.");
221
222 resetMediaPlayer();
223 unsetRingerAudioMode();
224 }
225
226 /**
227 * Stops and uninitializes the media player.
228 */
229 private void resetMediaPlayer() {
230 if (mMediaPlayer != null) {
231 // Ringtone.java does not do stop() before release, but it's safer to do so and none of
232 // the documentation suggests that stop() should be skipped.
233 mMediaPlayer.stop();
234 mMediaPlayer.release();
235 mMediaPlayer = null;
236 }
237 }
238
239 /**
240 * @return The default ringtone Uri.
241 */
242 private Uri getCurrentRingtoneUri() {
243 return RingtoneManager.getActualDefaultRingtoneUri(
244 TelecommApp.getInstance(), RingtoneManager.TYPE_RINGTONE);
245 }
246
247 /**
248 * Sets the audio mode for playing the ringtone.
249 */
250 private void setRingerAudioMode() {
251 AudioManager audioManager = getAudioManager();
252 audioManager.requestAudioFocusForCall(
253 AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
254 audioManager.setMode(AudioManager.MODE_RINGTONE);
255 }
256
257 /**
258 * Returns the audio mode to the normal state after ringing.
259 */
260 private void unsetRingerAudioMode() {
261 AudioManager audioManager = getAudioManager();
262 audioManager.setMode(AudioManager.MODE_NORMAL);
263 audioManager.abandonAudioFocusForCall();
264 }
265
266 /**
267 * Returns the system audio manager.
268 */
269 private AudioManager getAudioManager() {
270 return (AudioManager) TelecommApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800271 }
272}