Audio focus and ring changes (again).
- Went back to using ringtone. Ringtone class has fallback logic that is
necessary to keep (without duplicating).
- Moved the ringtone player code into AsyncRingtonePlayer and kept the
CallsManager listener code inside ringer into CallAudioManager.java
- Consolidated AudioFocus acquisition into CallAudioManager.
bug: 13365906
Change-Id: I8d7b6a999f594b8f81497aa3f5b7ac5916fdd18e
diff --git a/src/com/android/telecomm/AsyncRingtonePlayer.java b/src/com/android/telecomm/AsyncRingtonePlayer.java
new file mode 100644
index 0000000..ae78c5b
--- /dev/null
+++ b/src/com/android/telecomm/AsyncRingtonePlayer.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.telecomm;
+
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.provider.Settings;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
+ * used from the main thread.
+ */
+class AsyncRingtonePlayer {
+ // Message codes used with the ringtone thread.
+ static final int EVENT_PLAY = 1;
+ static final int EVENT_STOP = 2;
+
+ /** Handler running on the ringtone thread. */
+ private Handler mHandler;
+
+ /** The current ringtone. Only used by the ringtone thread. */
+ private Ringtone mRingtone;
+
+ /** Plays the ringtone. */
+ void play() {
+ Log.d(this, "Posting play.");
+ postMessage(EVENT_PLAY, true /* shouldCreateHandler */);
+ }
+
+ /** Stops playing the ringtone. */
+ void stop() {
+ Log.d(this, "Posting stop.");
+ postMessage(EVENT_STOP, false /* shouldCreateHandler */);
+ }
+
+ /**
+ * Posts a message to the ringtone-thread handler. Creates the handler if specified by the
+ * parameter shouldCreateHandler.
+ *
+ * @param messageCode The message to post.
+ * @param shouldCreateHandler True when a handler should be created to handle this message.
+ */
+ private void postMessage(int messageCode, boolean shouldCreateHandler) {
+ synchronized(this) {
+ if (mHandler == null && shouldCreateHandler) {
+ mHandler = getNewHandler();
+ }
+
+ if (mHandler == null) {
+ Log.d(this, "Message %d skipped because there is no handler.", messageCode);
+ } else {
+ mHandler.sendEmptyMessage(messageCode);
+ }
+ }
+ }
+
+ /**
+ * Creates a new ringtone Handler running in its own thread.
+ */
+ private Handler getNewHandler() {
+ Preconditions.checkState(mHandler == null);
+
+ HandlerThread thread = new HandlerThread("ringtone-player");
+ thread.start();
+
+ return new Handler(thread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case EVENT_PLAY:
+ handlePlay();
+ break;
+ case EVENT_STOP:
+ handleStop();
+ break;
+ }
+ }
+ };
+ }
+
+ /**
+ * Starts the actual playback of the ringtone. Executes on ringtone-thread.
+ */
+ private void handlePlay() {
+ ThreadUtil.checkNotOnMainThread();
+ Log.i(this, "Play ringtone.");
+
+ if (mRingtone == null) {
+ mRingtone = getCurrentRingtone();
+
+ // Cancel everything if there is no ringtone.
+ if (mRingtone == null) {
+ handleStop();
+ return;
+ }
+ }
+
+ if (mRingtone.isPlaying()) {
+ Log.d(this, "Ringtone already playing.");
+ } else {
+ mRingtone.play();
+ Log.d(this, "Ringtone.play() invoked.");
+ }
+ }
+
+ /**
+ * Stops the playback of the ringtone. Executes on the ringtone-thread.
+ */
+ private void handleStop() {
+ ThreadUtil.checkNotOnMainThread();
+ Log.i(this, "Stop ringtone.");
+
+ if (mRingtone != null) {
+ Log.d(this, "Ringtone.stop() invoked.");
+ mRingtone.stop();
+ mRingtone = null;
+ }
+
+ synchronized(this) {
+ if (mHandler.hasMessages(EVENT_PLAY)) {
+ Log.v(this, "Keeping alive ringtone thread for pending messages.");
+ } else {
+ mHandler.removeMessages(EVENT_STOP);
+ mHandler.getLooper().quitSafely();
+ mHandler = null;
+ Log.v(this, "Handler cleared.");
+ }
+ }
+ }
+
+ private Ringtone getCurrentRingtone() {
+ // TODO: Needs support for custom ringtones.
+ return RingtoneManager.getRingtone(
+ TelecommApp.getInstance(), Settings.System.DEFAULT_RINGTONE_URI);
+ }
+}
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
index 07d5dbe..0d58f86 100644
--- a/src/com/android/telecomm/CallAudioManager.java
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -20,40 +20,174 @@
import android.media.AudioManager;
import android.telecomm.CallState;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
/**
* This class manages audio modes, streams and other properties.
*/
final class CallAudioManager extends CallsManagerListenerBase {
+ private AsyncRingtonePlayer mRinger = new AsyncRingtonePlayer();
+
+ private boolean mHasAudioFocus = false;
+
+ /**
+ * Used to keep ordering of unanswered incoming calls. The existence of multiple call services
+ * means that there can easily exist multiple incoming calls and explicit ordering is useful for
+ * maintaining the proper state of the ringer.
+ */
+ private final List<String> mUnansweredCallIds = Lists.newLinkedList();
+
+ /**
+ * Denotes when the ringer is disabled. This is useful in temporarily disabling the ringer when
+ * the a call is answered/rejected by the user, but the call hasn't actually moved out of the
+ * ringing state.
+ */
+ private boolean mIsRingingDisabled = false;
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (call.getState() == CallState.RINGING) {
+ mUnansweredCallIds.add(call.getId());
+ }
+ updateAudio();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ removeFromUnansweredCallIds(call.getId());
+ updateAudio();
+ }
+
@Override
public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
- switch (newState) {
- case ACTIVE:
- updateAudioFocusForActiveCall(call);
- break;
- case DISCONNECTED:
- updateAudioFocusForNoCall();
- break;
- default:
- break;
+ if (oldState == CallState.RINGING) {
+ removeFromUnansweredCallIds(call.getId());
+ }
+
+ updateAudio();
+ }
+
+ @Override
+ public void onIncomingCallAnswered(Call call) {
+ mIsRingingDisabled = true;
+ updateAudio();
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call) {
+ mIsRingingDisabled = true;
+ updateAudio();
+ }
+
+ /**
+ * Reads the current state of all calls from CallsManager and sets the appropriate audio modes
+ * as well as triggers the start/stop of the ringer.
+ */
+ private void updateAudio() {
+ CallsManager callsManager = CallsManager.getInstance();
+
+ boolean hasRingingCall = !mIsRingingDisabled && !mUnansweredCallIds.isEmpty();
+ boolean hasLiveCall = callsManager.hasCallWithState(CallState.ACTIVE, CallState.DIALING);
+
+ int mode = hasRingingCall ? AudioManager.MODE_RINGTONE :
+ hasLiveCall ? AudioManager.MODE_IN_CALL :
+ AudioManager.MODE_NORMAL;
+
+ boolean needsFocus = (mode != AudioManager.MODE_NORMAL);
+
+ // Acquiring focus needs to be first, unlike releasing focus, which happens at the end.
+ if (needsFocus) {
+ acquireFocus(hasRingingCall);
+ setMode(mode);
+ }
+
+ if (hasRingingCall) {
+ mRinger.play();
+ } else {
+ mRinger.stop();
+ }
+
+ if (!needsFocus) {
+ setMode(AudioManager.MODE_NORMAL);
+ releaseFocus();
}
}
- private void updateAudioFocusForActiveCall(Call call) {
- Log.v(this, "onForegroundCallChanged, requesting audio focus");
- Context context = TelecommApp.getInstance();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- audioManager.setMode(AudioManager.MODE_IN_CALL);
- audioManager.setMicrophoneMute(false);
- audioManager.setSpeakerphoneOn(false);
+ /**
+ * Acquires audio focus.
+ *
+ * @param isForRinging True if this focus is for playing the ringer.
+ */
+ private void acquireFocus(boolean isForRinging) {
+ if (!mHasAudioFocus) {
+ int stream = isForRinging ? AudioManager.STREAM_RING : AudioManager.STREAM_VOICE_CALL;
+
+ AudioManager audioManager = getAudioManager();
+ audioManager.requestAudioFocusForCall(stream, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ audioManager.setMicrophoneMute(false);
+ audioManager.setSpeakerphoneOn(false);
+ mHasAudioFocus = true;
+ }
}
- private void updateAudioFocusForNoCall() {
- Log.v(this, "updateAudioFocusForNoCall, abandoning audio focus");
- Context context = TelecommApp.getInstance().getApplicationContext();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.setMode(AudioManager.MODE_NORMAL);
- audioManager.abandonAudioFocusForCall();
+ /**
+ * Releases focus.
+ */
+ void releaseFocus() {
+ if (mHasAudioFocus) {
+ AudioManager audioManager = getAudioManager();
+
+ // Reset speakerphone and mute in case they were changed by telecomm.
+ audioManager.setMicrophoneMute(false);
+ audioManager.setSpeakerphoneOn(false);
+ audioManager.abandonAudioFocusForCall();
+
+ mHasAudioFocus = false;
+ Log.v(this, "Focus released");
+ }
+
+ }
+
+ /**
+ * Sets the audio mode.
+ *
+ * @param mode Mode constant from AudioManager.MODE_*.
+ */
+ void setMode(int mode) {
+ if (mHasAudioFocus) {
+ AudioManager audioManager = getAudioManager();
+ if (mode != audioManager.getMode()) {
+ Log.v(this, "Audio mode set to %d.", mode);
+ audioManager.setMode(mode);
+ Log.v(this, "Audio mode actually set to %d.", audioManager.getMode());
+ }
+ } else {
+ Log.wtf(this, "Trying to set audio mode to %d without focus.", mode);
+ }
+ }
+
+ /**
+ * Removes the specified call from the list of unanswered incoming calls.
+ *
+ * @param callId The ID of the call.
+ */
+ private void removeFromUnansweredCallIds(String callId) {
+ if (!mUnansweredCallIds.isEmpty()) {
+ // If the call is the top-most call, then no longer disable the ringer.
+ if (callId.equals(mUnansweredCallIds.get(0))) {
+ mIsRingingDisabled = false;
+ }
+
+ mUnansweredCallIds.remove(callId);
+ }
+ }
+
+ /**
+ * Returns the system audio manager.
+ */
+ private AudioManager getAudioManager() {
+ return (AudioManager) TelecommApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
}
}
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 452f8e1..106f9af 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -78,8 +78,6 @@
private final CallsManagerListener mInCallController;
- private final CallsManagerListener mRinger;
-
private VoicemailManager mVoicemailManager;
private final List<OutgoingCallValidator> mOutgoingCallValidators = Lists.newArrayList();
@@ -95,7 +93,6 @@
mPhoneStateBroadcaster = new PhoneStateBroadcaster();
mCallAudioManager = new CallAudioManager();
mInCallController = new InCallController();
- mRinger = new Ringer();
}
static CallsManager getInstance() {
@@ -232,7 +229,6 @@
mPhoneStateBroadcaster.onIncomingCallAnswered(call);
mCallAudioManager.onIncomingCallAnswered(call);
mInCallController.onIncomingCallAnswered(call);
- mRinger.onIncomingCallAnswered(call);
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}. However, if we ever change that to look more responsive,
@@ -258,7 +254,6 @@
mPhoneStateBroadcaster.onIncomingCallRejected(call);
mCallAudioManager.onIncomingCallRejected(call);
mInCallController.onIncomingCallRejected(call);
- mRinger.onIncomingCallRejected(call);
call.reject();
}
@@ -342,6 +337,21 @@
}
/**
+ * @return True if there exists a call with the specific state.
+ */
+ boolean hasCallWithState(CallState... states) {
+ for (Call call : mCalls.values()) {
+ for (CallState state : states) {
+ if (call.getState() == state) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Adds the specified call to the main list of live calls.
*
* @param call The call to add.
@@ -353,7 +363,6 @@
mPhoneStateBroadcaster.onCallAdded(call);
mCallAudioManager.onCallAdded(call);
mInCallController.onCallAdded(call);
- mRinger.onCallAdded(call);
updateForegroundCall();
}
@@ -364,13 +373,11 @@
mPhoneStateBroadcaster.onCallRemoved(call);
mCallAudioManager.onCallRemoved(call);
mInCallController.onCallRemoved(call);
- mRinger.onCallRemoved(call);
updateForegroundCall();
}
/**
- * Sets the specified state on the specified call. Updates the ringer if the call is exiting
- * the RINGING state.
+ * Sets the specified state on the specified call.
*
* @param callId The ID of the call to update.
* @param newState The new state of the call.
@@ -389,8 +396,7 @@
}
/**
- * Sets the specified state on the specified call. Updates the ringer if the call is exiting
- * the RINGING state.
+ * Sets the specified state on the specified call.
*
* @param call The call.
* @param newState The new state of the call.
@@ -413,7 +419,6 @@
mPhoneStateBroadcaster.onCallStateChanged(call, oldState, newState);
mCallAudioManager.onCallStateChanged(call, oldState, newState);
mInCallController.onCallStateChanged(call, oldState, newState);
- mRinger.onCallStateChanged(call, oldState, newState);
updateForegroundCall();
}
}
@@ -444,7 +449,6 @@
mPhoneStateBroadcaster.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
mCallAudioManager.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
mInCallController.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
- mRinger.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
}
}
}
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
deleted file mode 100644
index bc24560..0000000
--- a/src/com/android/telecomm/Ringer.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright 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.telecomm;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.telecomm.CallState;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Controls ringing and vibration for incoming calls.
- */
-final class Ringer extends CallsManagerListenerBase implements OnErrorListener, OnPreparedListener {
- // States for the Ringer.
- /** Actively playing the ringer. */
- private static final int RINGING = 1;
-
- /** Ringer currently stopped. */
- private static final int STOPPED = 2;
-
- /** {@link #mMediaPlayer} is preparing, expected to ring once prepared. */
- private static final int PREPARING_WITH_RING = 3;
-
- /** {@link #mMediaPlayer} is preparing, expected to stop once prepared. */
- private static final int PREPARING_WITH_STOP = 4;
-
- /**
- * The current state of the ringer.
- */
- private int mState = STOPPED;
-
- /** The active media player for the ringer. */
- private MediaPlayer mMediaPlayer;
-
- /*
- * Used to keep ordering of unanswered incoming calls. The existence of multiple call services
- * means that there can easily exist multiple incoming calls and explicit ordering is useful for
- * maintaining the proper state of the ringer.
- */
- private final List<String> mUnansweredCallIds = Lists.newLinkedList();
-
- @Override
- public void onCallAdded(Call call) {
- if (call.isIncoming() && call.getState() == CallState.RINGING) {
- if (mUnansweredCallIds.contains(call.getId())) {
- Log.wtf(this, "New ringing call is already in list of unanswered calls");
- }
- mUnansweredCallIds.add(call.getId());
- if (mUnansweredCallIds.size() == 1) {
- // Start the ringer if we are the top-most incoming call (the only one in this
- // case).
- startRinging();
- }
- }
- }
-
- @Override
- public void onCallRemoved(Call call) {
- removeFromUnansweredCallIds(call.getId());
- }
-
- @Override
- public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
- if (newState != CallState.RINGING) {
- removeFromUnansweredCallIds(call.getId());
- }
- }
-
- @Override
- public void onIncomingCallAnswered(Call call) {
- onRespondedToIncomingCall(call);
- }
-
- @Override
- public void onIncomingCallRejected(Call call) {
- onRespondedToIncomingCall(call);
- }
-
- private void onRespondedToIncomingCall(Call call) {
- // Only stop the ringer if this call is the top-most incoming call.
- if (!mUnansweredCallIds.isEmpty() && mUnansweredCallIds.get(0).equals(call.getId())) {
- stopRinging();
- }
- }
-
- /**
- * Removes the specified call from the list of unanswered incoming calls and updates the ringer
- * based on the new state of {@link #mUnansweredCallIds}. Safe to call with a call ID that
- * is not present in the list of incoming calls.
- *
- * @param callId The ID of the call.
- */
- private void removeFromUnansweredCallIds(String callId) {
- if (mUnansweredCallIds.remove(callId)) {
- if (mUnansweredCallIds.isEmpty()) {
- stopRinging();
- } else {
- startRinging();
- }
- }
- }
-
- /**
- * Starts the vibration, ringer, and/or call-waiting tone.
- * TODO(santoscordon): vibration and call-waiting tone.
- */
- private void startRinging() {
- // Check if we are muted before playing the ringer.
- if (getAudioManager().getStreamVolume(AudioManager.STREAM_RING) > 0) {
- moveToState(RINGING);
- } else {
- Log.d(this, "Ringer play skipped due to muted volume.");
- }
- }
-
- /**
- * Stops the vibration, ringer, and/or call-waiting tone.
- */
- private void stopRinging() {
- moveToState(STOPPED);
- }
-
- /**
- * Handles asynchronous media player "prepared" response by playing the ringer if we are
- * still expected to or uninitializing it if we've been asked to stop.
- *
- * {@inheritDoc}
- */
- @Override
- public void onPrepared(MediaPlayer mediaPlayer) {
- Preconditions.checkState(mMediaPlayer == null);
-
- // See {@link #moveToState} for state transitions.
- if (PREPARING_WITH_RING == mState) {
- Log.i(this, "Playing the ringer.");
- setRingerAudioMode();
- mMediaPlayer = mediaPlayer;
- mMediaPlayer.start();
- setState(RINGING);
- } else if (PREPARING_WITH_STOP == mState) {
- mediaPlayer.release();
- setState(STOPPED);
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
- Log.i(this, "Mediaplayer failed to initialize. What: %d, extra: %d.", what, extra);
- resetMediaPlayer();
- setState(STOPPED);
- return true;
- }
-
- /**
- * Transitions the state of the ringer. State machine below. Any missing arrows imply that the
- * state remains the same (e.g., (r) on RING state keeps it at RING state).
- *
- * +----------------(s)----------------------------+
- * | |
- * +----------------(e)-------+ |
- * | | |
- * +-> STOPPED -(r)-> PREPARING_WITH_RING +-(p)-> RING
- * ^ ^ |
- * | | |
- * (p,e) (r) |
- * | | +-(s)-> PREPARING_WITH_STOP
- * | | | |
- * | +----------------------+ |
- * +---------------------------------------------+
- *
- * STOPPED - Ringer completely stopped, like its initial state.
- * PREPARING_TO_RING - Media player preparing asynchronously to start ringing.
- * RINGING - The ringtone is currently playing.
- * PREPARING_TO_STOP - Media player is still preparing, but we've already been asked to stop.
- *
- * (r) - {@link #startRinging}
- * (s) - {@link #stopRinging}
- * (p) - {@link #onPrepared}
- * (e) - {@link #onError}
- */
- private void moveToState(int newState) {
- // Only this method sets PREPARING_* states.
- Preconditions.checkState(newState == RINGING || newState == STOPPED);
-
- if (newState == mState) {
- return;
- }
-
- if (RINGING == newState) {
- if (STOPPED == mState) {
- // If we are stopped, we need to preparing the media player and wait for it to
- // start the ring. New state set by prepareForRinging.
- if (prepareForRinging()) {
- setState(PREPARING_WITH_RING);
- }
- } else if (PREPARING_WITH_STOP == mState) {
- // We are currently preparing the media player, but expect it to put the ringer into
- // stop once prepared...change that to ring.
- setState(PREPARING_WITH_RING);
- }
- } else if (STOPPED == newState) {
- if (RINGING == mState) {
- // We are currently ringing, so just stop it.
- stopPlayingRinger();
- setState(STOPPED);
- } else if (PREPARING_WITH_RING == mState) {
- // We are preparing the media player, make sure that when it is finished, it moves
- // to STOPPED instead of ringing.
- setState(PREPARING_WITH_STOP);
- }
- }
- }
-
- /**
- * Sets the ringer state and checks the current thread.
- *
- * @param newState The new state to set.
- */
- private void setState(int newState) {
- ThreadUtil.checkOnMainThread();
- Log.v(this, "setState, %d -> %d", mState, newState);
- mState = newState;
- }
-
- /**
- * Starts media player's asynchronous prepare. Response returned in either {@link #onError} or
- * {@link #onPrepared}.
- *
- * @return True if the prepare was successfully started.
- */
- private boolean prepareForRinging() {
- Log.i(this, "Preparing the ringer.");
-
- Uri ringtoneUri = getCurrentRingtoneUri();
- if (ringtoneUri == null) {
- Log.e(this, null, "Ringtone not set.");
- return false;
- }
-
- MediaPlayer mediaPlayer = new MediaPlayer();
- mediaPlayer.setOnErrorListener(this);
- mediaPlayer.setOnPreparedListener(this);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
-
- try {
- mediaPlayer.setDataSource(TelecommApp.getInstance(), ringtoneUri);
- mediaPlayer.prepareAsync();
- return true;
- } catch (IOException e) {
- mediaPlayer.reset();
- mediaPlayer.release();
-
- Log.e(this, e, "Failed to initialize media player for ringer: %s.", ringtoneUri);
- return false;
- }
- }
-
- /**
- * Stops and uninitializes the media player.
- */
- private void stopPlayingRinger() {
- Preconditions.checkNotNull(mMediaPlayer);
- Log.i(this, "Stopping the ringer.");
-
- resetMediaPlayer();
- unsetRingerAudioMode();
- }
-
- /**
- * Stops and uninitializes the media player.
- */
- private void resetMediaPlayer() {
- if (mMediaPlayer != null) {
- // Ringtone.java does not do stop() before release, but it's safer to do so and none of
- // the documentation suggests that stop() should be skipped.
- mMediaPlayer.stop();
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
- }
-
- /**
- * @return The default ringtone Uri.
- */
- private Uri getCurrentRingtoneUri() {
- return RingtoneManager.getActualDefaultRingtoneUri(
- TelecommApp.getInstance(), RingtoneManager.TYPE_RINGTONE);
- }
-
- /**
- * Sets the audio mode for playing the ringtone.
- */
- private void setRingerAudioMode() {
- AudioManager audioManager = getAudioManager();
- audioManager.requestAudioFocusForCall(
- AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- audioManager.setMode(AudioManager.MODE_RINGTONE);
- }
-
- /**
- * Returns the audio mode to the normal state after ringing.
- */
- private void unsetRingerAudioMode() {
- AudioManager audioManager = getAudioManager();
- audioManager.setMode(AudioManager.MODE_NORMAL);
- audioManager.abandonAudioFocusForCall();
- }
-
- /**
- * Returns the system audio manager.
- */
- private AudioManager getAudioManager() {
- return (AudioManager) TelecommApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
- }
-}