Add Ringer skeleton to CallsManager.
Change-Id: Ica6f5236eb63896b1e656876879d039907c42e9e
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index c049e18..a592ee3 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -46,6 +46,8 @@
/** Used to control the in-call app. */
private final InCallController mInCallController;
+ private final Ringer mRinger;
+
/**
* The main call repository. Keeps an instance of all live calls keyed by call ID. New incoming
* and outgoing calls are added to the map and removed when the calls move to the disconnected
@@ -55,6 +57,15 @@
private final Map<String, Call> mCalls = Maps.newHashMap();
/**
+ * 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.
+ * TODO(santoscordon): May want to add comments about ITelephony.answerCall() method since
+ * ordering may also apply to that case.
+ */
+ private final List<Call> mUnansweredIncomingCalls = Lists.newLinkedList();
+
+ /**
* May be unnecessary per off-line discussions (between santoscordon and gilad) since the set
* of CallsManager APIs that need to be exposed to the dialer (or any application firing call
* intents) may be empty.
@@ -77,6 +88,7 @@
private CallsManager() {
mSwitchboard = new Switchboard(this);
mInCallController = new InCallController(this);
+ mRinger = new Ringer();
}
static CallsManager getInstance() {
@@ -123,6 +135,12 @@
// No objection to accept the incoming call, proceed with potentially connecting it (based
// on the user's action, or lack thereof).
addCall(call);
+
+ mUnansweredIncomingCalls.add(call);
+ if (mUnansweredIncomingCalls.size() == 1) {
+ // Start the ringer if we are the top-most incoming call (the only one in this case).
+ mRinger.startRinging();
+ }
}
/**
@@ -173,6 +191,8 @@
if (call == null) {
Log.i(TAG, "Request to answer a non-existent call " + callId);
} else {
+ stopRinging(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,
// then we need to make sure we add a timeout for the answer() in case the call never
@@ -193,6 +213,7 @@
if (call == null) {
Log.i(TAG, "Request to reject a non-existent call " + callId);
} else {
+ stopRinging(call);
call.reject();
}
}
@@ -224,6 +245,7 @@
void markCallAsActive(String callId) {
setCallState(callId, CallState.ACTIVE);
+ removeFromUnansweredCalls(callId);
mInCallController.markCallAsActive(callId);
}
@@ -235,6 +257,7 @@
*/
void markCallAsDisconnected(String callId) {
setCallState(callId, CallState.DISCONNECTED);
+ removeFromUnansweredCalls(callId);
Call call = mCalls.remove(callId);
// At this point the call service has confirmed that the call is disconnected to it is
@@ -280,29 +303,65 @@
}
/**
- * Sets the specified state on the specified call.
+ * Sets the specified state on the specified call. Updates the ringer if the call is exiting
+ * the RINGING state.
*
* @param callId The ID of the call to update.
- * @param state The new state of the call.
+ * @param newState The new state of the call.
*/
- private void setCallState(String callId, CallState state) {
+ private void setCallState(String callId, CallState newState) {
Preconditions.checkState(!Strings.isNullOrEmpty(callId));
- Preconditions.checkNotNull(state);
+ Preconditions.checkNotNull(newState);
Call call = mCalls.get(callId);
if (call == null) {
- Log.e(TAG, "Call " + callId + " was not found while attempting to upda the state to " +
- state + ".");
+ Log.e(TAG, "Call " + callId + " was not found while attempting to update the state " +
+ "to " + newState + ".");
} else {
- // Unfortunately, in the telephony world, the radio is king. So if the call notifies us
- // that the call is in a particular state, we allow it even if it doesn't make sense
- // (e.g., ACTIVE -> RINGING).
- // TODO(santoscordon): Consider putting a stop to the above and turning CallState into
- // a well-defined state machine.
- // TODO(santoscordon): Define expected state transitions here, and log when an
- // unexpected transition occurs.
- call.setState(state);
- // TODO(santoscordon): Notify the in-call app whenever a call changes state.
+ if (newState != call.getState()) {
+ // Unfortunately, in the telephony world the radio is king. So if the call notifies
+ // us that the call is in a particular state, we allow it even if it doesn't make
+ // sense (e.g., ACTIVE -> RINGING).
+ // TODO(santoscordon): Consider putting a stop to the above and turning CallState
+ // into a well-defined state machine.
+ // TODO(santoscordon): Define expected state transitions here, and log when an
+ // unexpected transition occurs.
+ call.setState(newState);
+ // TODO(santoscordon): Notify the in-call app whenever a call changes state.
+ }
+ }
+ }
+
+ /**
+ * Removes the specified call from the list of unanswered incoming calls and updates the ringer
+ * based on the new state of {@link #mUnansweredIncomingCalls}. 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 removeFromUnansweredCalls(String callId) {
+ Call call = mCalls.get(callId);
+ if (call != null && mUnansweredIncomingCalls.remove(call)) {
+ if (mUnansweredIncomingCalls.isEmpty()) {
+ mRinger.stopRinging();
+ } else {
+ mRinger.startRinging();
+ }
+ }
+ }
+
+ /**
+ * Stops playing the ringer if the specified call is the top-most incoming call. This exists
+ * separately from {@link #removeIncomingCall} for cases where we would like to stop playing the
+ * ringer for a call, but that call may still exist in {@link #mUnansweredIncomingCalls} - See
+ * {@link #rejectCall}, {@link #answerCall}.
+ *
+ * @param call The call for which we should stop ringing.
+ */
+ private void stopRinging(Call call) {
+ // Only stop the ringer if this call is the top-most incoming call.
+ if (!mUnansweredIncomingCalls.isEmpty() && mUnansweredIncomingCalls.get(0) == call) {
+ mRinger.stopRinging();
}
}
}
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
new file mode 100644
index 0000000..35575eb
--- /dev/null
+++ b/src/com/android/telecomm/Ringer.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Controls ringing and vibration for incoming calls.
+ *
+ * TODO(santoscordon): Consider moving all ringing responsibility to InCall app as an implementation
+ * within InCallServiceBase.
+ */
+final class Ringer {
+
+ /**
+ * Starts the vibration, ringer, and/or call-waiting tone.
+ */
+ void startRinging() {
+ // TODO(santoscordon): Fill in.
+ }
+
+ /**
+ * Stops the vibration, ringer, and/or call-waiting tone.
+ */
+ void stopRinging() {
+ // TODO(santoscordon): Fill in.
+ }
+}