Merge "Fix missed call notification tests"
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index babed6a..1cdd8c1 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,7 +20,7 @@
<string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telèfon"</string>
<string name="unknown" msgid="6878797917991465859">"Desconegut"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Trucada perduda"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Trucada de feina perduda"</string>
+ <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Trucada perduda de feina"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Trucades perdudes"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> trucades perdudes"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Trucada perduda de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 87f7805..052c5fb 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -20,7 +20,7 @@
<string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Белгисиз"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Кабыл алынбаган чалуу"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Жумуш боюнча кабыл алынбаган чалуу"</string>
+ <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Кабыл алынбай калган чалуу (жумуш)"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Кабыл алынбаган чалуулар"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> кабыл алынбаган чалуу"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> дегенден кабыл алынбаган чалуу"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 66bc610..87bf156 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -20,7 +20,7 @@
<string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Утас"</string>
<string name="unknown" msgid="6878797917991465859">"Тодорхойгүй"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Аваагүй дуудлага"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Аваагүй ажлын дуудлага"</string>
+ <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Аваагүй албаны дуудлага"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Аваагүй дуудлагууд"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> аваагүй дуудлага"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-н аваагүй дуудлага"</string>
diff --git a/src/com/android/server/telecom/BluetoothManager.java b/src/com/android/server/telecom/BluetoothManager.java
index 389d0f6..82c8d47 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -46,16 +46,26 @@
new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
- updateBluetoothState();
+ Log.startSession("BMSL.oSC");
+ try {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
+ updateBluetoothState();
+ } finally {
+ Log.endSession();
+ }
}
@Override
public void onServiceDisconnected(int profile) {
- mBluetoothHeadset = null;
- Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
- updateBluetoothState();
+ Log.startSession("BMSL.oSD");
+ try {
+ mBluetoothHeadset = null;
+ Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
+ updateBluetoothState();
+ } finally {
+ Log.endSession();
+ }
}
};
@@ -65,21 +75,26 @@
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ Log.startSession("BM.oR");
+ try {
+ String action = intent.getAction();
- if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
- int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
- BluetoothHeadset.STATE_DISCONNECTED);
- Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
- Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
- updateBluetoothState();
- } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
- int bluetoothHeadsetAudioState =
- intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
- BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
- Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
- Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
- updateBluetoothState();
+ if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+ int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_DISCONNECTED);
+ Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+ Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
+ updateBluetoothState();
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ int bluetoothHeadsetAudioState =
+ intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+ Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
+ updateBluetoothState();
+ }
+ } finally {
+ Log.endSession();
}
}
};
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 7159b49..6680eb6 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -326,12 +326,6 @@
}
@Override
- public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
- // The BluetoothPhoneService does not need to respond to changes in foreground calls,
- // which are always accompanied by call state changes anyway.
- }
-
- @Override
public void onIsConferencedChanged(Call call) {
/*
* Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 5b3853e..f392e22 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -11,216 +11,268 @@
* 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.
+ * limitations under the License
*/
package com.android.server.telecom;
-import android.content.Context;
-import android.media.AudioManager;
+import android.annotation.NonNull;
import android.media.IAudioService;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
+import android.media.ToneGenerator;
import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-/**
- * This class manages audio modes, streams and other properties.
- */
-@VisibleForTesting
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.LinkedHashSet;
+
public class CallAudioManager extends CallsManagerListenerBase {
- private static final int STREAM_NONE = -1;
-
- private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE";
- private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM";
- private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO";
- private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF";
- private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC";
- private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION";
- private static final String STREAM_DESCRIPTION_RING = "STREAM_RING";
- private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM";
- private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL";
-
- private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID";
- private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT";
- private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL";
- private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE";
- private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL";
- private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION";
-
- private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0;
- private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2;
- private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4;
- private static final int MSG_AUDIO_MANAGER_SET_MODE = 5;
-
- private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) {
-
- private AudioManager mAudioManager;
-
- @Override
- public void handleMessage(Message msg) {
- SomeArgs args = (SomeArgs) msg.obj;
- int arg2 = 0;
- try {
- if (args != null) {
- Session subsession = (Session) args.arg1;
- Log.continueSession(subsession, "CAM.hM_" + msg.what);
- arg2 = (int) args.arg2;
- }
- switch (msg.what) {
- case MSG_AUDIO_MANAGER_INITIALIZE: {
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- break;
- }
- case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: {
- mAudioManager.abandonAudioFocusForCall();
- break;
- }
- case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: {
- int stream = arg2;
- mAudioManager.requestAudioFocusForCall(
- stream,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- break;
- }
- case MSG_AUDIO_MANAGER_SET_MODE: {
- int newMode = arg2;
- int oldMode = mAudioManager.getMode();
- Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode),
- modeToString(newMode));
-
- if (oldMode != newMode) {
- if (oldMode == AudioManager.MODE_IN_CALL &&
- newMode == AudioManager.MODE_RINGTONE) {
- Log.i(this, "Transition from IN_CALL -> RINGTONE."
- + " Resetting to NORMAL first.");
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
- }
- mAudioManager.setMode(newMode);
- synchronized (mLock) {
- mMostRecentlyUsedMode = newMode;
- }
- }
- break;
- }
- default:
- break;
- }
- } finally {
- Log.endSession();
- args.recycle();
- }
- }
- };
public interface AudioServiceFactory {
IAudioService getAudioService();
}
- private final Context mContext;
- private final TelecomSystem.SyncRoot mLock;
- private final CallsManager mCallsManager;
+ private final String LOG_TAG = CallAudioManager.class.getSimpleName();
+
+ private final LinkedHashSet<Call> mActiveOrDialingCalls;
+ private final LinkedHashSet<Call> mRingingCalls;
+ private final LinkedHashSet<Call> mHoldingCalls;
+ private final Set<Call> mCalls;
+ private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
+
private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ private final CallAudioModeStateMachine mCallAudioModeStateMachine;
+ private final CallsManager mCallsManager;
+ private final InCallTonePlayer.Factory mPlayerFactory;
+ private final Ringer mRinger;
+ private final RingbackPlayer mRingbackPlayer;
+ private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
- private int mAudioFocusStreamType;
- private boolean mIsRinging;
- private boolean mIsTonePlaying;
- private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
- private Call mCallToSpeedUpMTAudio = null;
+ private Call mForegroundCall;
+ private boolean mIsTonePlaying = false;
- public CallAudioManager(
- Context context,
- TelecomSystem.SyncRoot lock,
+ public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
CallsManager callsManager,
- CallAudioRouteStateMachine callAudioRouteStateMachine) {
- mContext = context;
- mLock = lock;
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE,
- setArgs(0)).sendToTarget();
- mCallsManager = callsManager;
- mAudioFocusStreamType = STREAM_NONE;
+ CallAudioModeStateMachine callAudioModeStateMachine,
+ InCallTonePlayer.Factory playerFactory,
+ Ringer ringer,
+ RingbackPlayer ringbackPlayer,
+ DtmfLocalTonePlayer dtmfLocalTonePlayer) {
+ mActiveOrDialingCalls = new LinkedHashSet<>();
+ mRingingCalls = new LinkedHashSet<>();
+ mHoldingCalls = new LinkedHashSet<>();
+ mCalls = new HashSet<>();
+ mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
+ put(CallState.ACTIVE, mActiveOrDialingCalls);
+ put(CallState.DIALING, mActiveOrDialingCalls);
+ put(CallState.RINGING, mRingingCalls);
+ put(CallState.ON_HOLD, mHoldingCalls);
+ }};
mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+ mCallAudioModeStateMachine = callAudioModeStateMachine;
+ mCallsManager = callsManager;
+ mPlayerFactory = playerFactory;
+ mRinger = ringer;
+ mRingbackPlayer = ringbackPlayer;
+ mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
+
+ mPlayerFactory.setCallAudioManager(this);
+ mCallAudioModeStateMachine.setCallAudioManager(this);
}
- private SomeArgs setArgs(int arg) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = Log.createSubsession();
- args.arg2 = arg;
- return args;
- }
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (call.getParentCall() != null) {
+ // No audio management for calls in a conference.
+ return;
+ }
+ Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
+ CallState.toString(oldState), CallState.toString(newState));
- @VisibleForTesting
- public CallAudioState getCallAudioState() {
- return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+ if (mCallStateToCalls.get(oldState) != null) {
+ mCallStateToCalls.get(oldState).remove(call);
+ }
+ if (mCallStateToCalls.get(newState) != null) {
+ mCallStateToCalls.get(newState).add(call);
+ }
+
+ updateForegroundCall();
+ if (newState == CallState.DISCONNECTED) {
+ playToneForDisconnectedCall(call);
+ }
+
+ onCallLeavingState(call, oldState);
+ onCallEnteringState(call, newState);
}
@Override
public void onCallAdded(Call call) {
- Log.v(this, "onCallAdded");
- onCallUpdated(call);
-
- if (hasFocus() && getForegroundCall() == call) {
- if (!call.isIncoming()) {
- // Unmute new outgoing call.
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
- CallAudioRouteStateMachine.MUTE_OFF);
- }
+ if (call.getParentCall() != null) {
+ return; // Don't do audio handling for calls in a conference.
}
+
+ if (mCalls.contains(call)) {
+ Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
+ return; // No guarantees that the same call won't get added twice.
+ }
+
+ Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
+ CallState.toString(call.getState()));
+
+ if (mCallStateToCalls.get(call.getState()) != null) {
+ mCallStateToCalls.get(call.getState()).add(call);
+ }
+ updateForegroundCall();
+ mCalls.add(call);
+
+ onCallEnteringState(call, call.getState());
}
@Override
public void onCallRemoved(Call call) {
- Log.v(this, "onCallRemoved");
+ if (call.getParentCall() != null) {
+ return; // Don't do audio handling for calls in a conference.
+ }
+
+ if (!mCalls.contains(call)) {
+ return; // No guarantees that the same call won't get removed twice.
+ }
+
+ Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
+ CallState.toString(call.getState()));
+
+ if (mCallStateToCalls.get(call.getState()) != null) {
+ mCallStateToCalls.get(call.getState()).remove(call);
+ }
+
+ updateForegroundCall();
+ mCalls.remove(call);
+
+ onCallLeavingState(call, call.getState());
+
if (mCallsManager.getCalls().isEmpty()) {
Log.v(this, "all calls removed, resetting system audio to default state");
mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.REINITIALIZE);
}
-
- // If we didn't already have focus, there's nothing to do.
- if (hasFocus()) {
- updateAudioStreamAndMode(call);
- }
- }
-
- @Override
- public void onCallStateChanged(Call call, int oldState, int newState) {
- Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState);
- onCallUpdated(call);
}
@Override
public void onIncomingCallAnswered(Call call) {
- Log.v(this, "onIncomingCallAnswered");
-
- if (mCallsManager.getCalls().size() == 1) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
- CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
- }
+ // This is called after the UI answers the call, but before the connection service
+ // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
- Log.v(this, "Speed up audio setup for IMS MT call.");
- mCallToSpeedUpMTAudio = call;
- updateAudioStreamAndMode(call);
+ if (mForegroundCall == call) {
+ Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
+ "an active in-call audio state before connection service has " +
+ "connected the call.");
+ if (mCallStateToCalls.get(call.getState()) != null) {
+ mCallStateToCalls.get(call.getState()).remove(call);
+ }
+ mActiveOrDialingCalls.add(call);
+ mCallAudioModeStateMachine.sendMessage(
+ CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
+ makeArgsForModeStateMachine());
+ }
+ }
+
+ if (mRingingCalls.size() == 0) {
+ mRinger.stopRinging();
+ mRinger.stopCallWaiting();
}
}
@Override
- public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
- onCallUpdated(newForegroundCall);
- // Ensure that the foreground call knows about the latest audio state.
- updateAudioForForegroundCall();
+ public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+ if (videoProfile == null) {
+ return;
+ }
+
+ if (call != mForegroundCall) {
+ // We only play tones for foreground calls.
+ return;
+ }
+
+ int previousVideoState = call.getVideoState();
+ int newVideoState = videoProfile.getVideoState();
+ Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
+ .videoStateToString(newVideoState));
+
+ boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
+ VideoProfile.isReceptionEnabled(newVideoState);
+
+ if (isUpgradeRequest) {
+ mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+ }
}
@Override
public void onIsVoipAudioModeChanged(Call call) {
- updateAudioStreamAndMode(call);
+ if (call != mForegroundCall) {
+ return;
+ }
+ mCallAudioModeStateMachine.sendMessage(
+ CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
+ makeArgsForModeStateMachine());
+ }
+
+ @Override
+ public void onRingbackRequested(Call call, boolean shouldRingback) {
+ if (call == mForegroundCall && shouldRingback) {
+ mRingbackPlayer.startRingbackForCall(call);
+ } else {
+ mRingbackPlayer.stopRingbackForCall(call);
+ }
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
+ // This gets called after the UI rejects a call but before the CS processes the rejection.
+ // Will get called before the state change from ringing to not ringing.
+
+ if (mRingingCalls.size() == 0 || call == mRingingCalls.iterator().next()) {
+ mRinger.stopRinging();
+ mRinger.stopCallWaiting();
+ }
+ }
+
+ @Override
+ public void onIsConferencedChanged(Call call) {
+ // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
+ Call parentCall = call.getParentCall();
+ if (parentCall == null) {
+ // Indicates that the call should be tracked for audio purposes. Treat it as if it were
+ // just added.
+ Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
+ " now be tracked by CallAudioManager.");
+ onCallAdded(call);
+ } else {
+ // The call joined a conference, so stop tracking it.
+ if (mCallStateToCalls.get(call.getState()) != null) {
+ mCallStateToCalls.get(call.getState()).remove(call);
+ }
+
+ updateForegroundCall();
+ mCalls.remove(call);
+ }
+ }
+
+ public CallAudioState getCallAudioState() {
+ return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+ }
+
+ public Call getForegroundCall() {
+ if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
+ return mForegroundCall;
+ }
+ return null;
}
void toggleMute() {
@@ -229,10 +281,6 @@
}
void mute(boolean shouldMute) {
- if (!hasFocus()) {
- return;
- }
-
Log.v(this, "mute, shouldMute: %b", shouldMute);
// Don't mute if there are any emergency calls.
@@ -278,239 +326,232 @@
}
}
- /**
- * Sets the audio stream and mode based on whether a call is ringing.
- *
- * @param call The call which changed ringing state.
- * @param isRinging {@code true} if the call is ringing, {@code false} otherwise.
- */
+ void silenceRingers() {
+ for (Call call : mRingingCalls) {
+ call.silence();
+ }
+
+ mRingingCalls.clear();
+ mRinger.stopRinging();
+ mRinger.stopCallWaiting();
+ mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ makeArgsForModeStateMachine());
+ }
+
+ void startRinging() {
+ mRinger.startRinging(mForegroundCall);
+ }
+
+ void startCallWaiting() {
+ mRinger.startCallWaiting(mRingingCalls.iterator().next());
+ }
+
+ void stopRinging() {
+ mRinger.stopRinging();
+ }
+
+ void stopCallWaiting() {
+ mRinger.stopCallWaiting();
+ }
+
+ void setCallAudioRouteFocusState(int focusState) {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Active or dialing calls:");
+ pw.increaseIndent();
+ dumpCallsInCollection(pw, mActiveOrDialingCalls);
+ pw.decreaseIndent();
+
+ pw.println("Ringing calls:");
+ pw.increaseIndent();
+ dumpCallsInCollection(pw, mRingingCalls);
+ pw.decreaseIndent();
+
+ pw.println("Holding calls:");
+ pw.increaseIndent();
+ dumpCallsInCollection(pw, mHoldingCalls);
+ pw.decreaseIndent();
+
+ pw.println("Foreground call:");
+ pw.println(mForegroundCall);
+ }
+
@VisibleForTesting
- public void setIsRinging(Call call, boolean isRinging) {
- if (mIsRinging != isRinging) {
- Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
- mIsRinging = isRinging;
- updateAudioStreamAndMode(call);
+ public void setIsTonePlaying(boolean isTonePlaying) {
+ mIsTonePlaying = isTonePlaying;
+ mCallAudioModeStateMachine.sendMessage(
+ isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
+ : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
+ makeArgsForModeStateMachine());
+ }
+
+ private void onCallLeavingState(Call call, int state) {
+ switch (state) {
+ case CallState.ACTIVE:
+ onCallLeavingActiveOrDialing();
+ break;
+ case CallState.RINGING:
+ onCallLeavingRinging();
+ break;
+ case CallState.ON_HOLD:
+ onCallLeavingHold();
+ break;
+ case CallState.DIALING:
+ stopRingbackForCall(call);
+ onCallLeavingActiveOrDialing();
}
}
- /**
- * Sets the tone playing status. Some tones can play even when there are no live calls and this
- * status indicates that we should keep audio focus even for tones that play beyond the life of
- * calls.
- *
- * @param isPlayingNew The status to set.
- */
- void setIsTonePlaying(boolean isPlayingNew) {
- if (mIsTonePlaying != isPlayingNew) {
- Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
- mIsTonePlaying = isPlayingNew;
- updateAudioStreamAndMode();
+ private void onCallEnteringState(Call call, int state) {
+ switch (state) {
+ case CallState.ACTIVE:
+ onCallEnteringActiveOrDialing();
+ break;
+ case CallState.RINGING:
+ onCallEnteringRinging();
+ break;
+ case CallState.ON_HOLD:
+ onCallEnteringHold();
+ break;
+ case CallState.DIALING:
+ onCallEnteringActiveOrDialing();
+ playRingbackForCall(call);
+ break;
}
}
- private void onCallUpdated(Call call) {
- updateAudioStreamAndMode(call);
- if (call != null && call.getState() == CallState.ACTIVE &&
- call == mCallToSpeedUpMTAudio) {
- mCallToSpeedUpMTAudio = null;
+ private void onCallLeavingActiveOrDialing() {
+ if (mActiveOrDialingCalls.size() == 0) {
+ mCallAudioModeStateMachine.sendMessage(
+ CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
+ makeArgsForModeStateMachine());
}
}
- private void updateAudioStreamAndMode() {
- updateAudioStreamAndMode(null /* call */);
+ private void onCallLeavingRinging() {
+ if (mRingingCalls.size() == 0) {
+ mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ makeArgsForModeStateMachine());
+ }
}
- private void updateAudioStreamAndMode(Call callToUpdate) {
- Log.i(this, "updateAudioStreamAndMode : mIsRinging: %b, mIsTonePlaying: %b, call: %s",
- mIsRinging, mIsTonePlaying, callToUpdate);
+ private void onCallLeavingHold() {
+ if (mHoldingCalls.size() == 0) {
+ mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+ makeArgsForModeStateMachine());
+ }
+ }
- if (mIsRinging) {
- Log.i(this, "updateAudioStreamAndMode : ringing");
- requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
+ private void onCallEnteringActiveOrDialing() {
+ if (mActiveOrDialingCalls.size() == 1) {
+ mCallAudioModeStateMachine.sendMessage(
+ CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
+ makeArgsForModeStateMachine());
+ }
+ }
+
+ private void onCallEnteringRinging() {
+ if (mRingingCalls.size() == 1) {
+ mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+ makeArgsForModeStateMachine());
+ }
+ }
+
+ private void onCallEnteringHold() {
+ if (mHoldingCalls.size() == 1) {
+ mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+ makeArgsForModeStateMachine());
+ }
+ }
+
+ private void updateForegroundCall() {
+ Call oldForegroundCall = mForegroundCall;
+ if (mActiveOrDialingCalls.size() > 0) {
+ mForegroundCall = mActiveOrDialingCalls.iterator().next();
+ } else if (mRingingCalls.size() > 0) {
+ mForegroundCall = mRingingCalls.iterator().next();
+ } else if (mHoldingCalls.size() > 0) {
+ mForegroundCall = mHoldingCalls.iterator().next();
} else {
- Call foregroundCall = getForegroundCall();
- Call waitingForAccountSelectionCall = mCallsManager
- .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT);
- Call call = mCallsManager.getForegroundCall();
- if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
- Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio.");
- requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
- AudioManager.MODE_IN_CALL);
- } else if (foregroundCall != null && !foregroundCall.isDisconnected() &&
- waitingForAccountSelectionCall == null) {
- // In the case where there is a call that is waiting for account selection,
- // this will fall back to abandonAudioFocus() below, which temporarily exits
- // the in-call audio mode. This is to allow TalkBack to speak the "Call with"
- // dialog information at media volume as opposed to through the earpiece.
- // Once exiting the "Call with" dialog, the audio focus will return to an in-call
- // audio mode when this method (updateAudioStreamAndMode) is called again.
- int mode = foregroundCall.getIsVoipAudioMode() ?
- AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
- Log.v(this, "updateAudioStreamAndMode : foreground");
- requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
- } else if (mIsTonePlaying) {
- // There is no call, however, we are still playing a tone, so keep focus.
- // Since there is no call from which to determine the mode, use the most
- // recently used mode instead.
- Log.v(this, "updateAudioStreamAndMode : tone playing");
- requestAudioFocusAndSetMode(
- AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
- } else if (!hasRingingForegroundCall() && mCallsManager.hasOnlyDisconnectedCalls()) {
- Log.v(this, "updateAudioStreamAndMode : no ringing call");
- abandonAudioFocus();
- } else {
- // mIsRinging is false, but there is a foreground ringing call present. Don't
- // abandon audio focus immediately to prevent audio focus from getting lost between
- // the time it takes for the foreground call to transition from RINGING to ACTIVE/
- // DISCONNECTED. When the call eventually transitions to the next state, audio
- // focus will be correctly abandoned by the if clause above.
+ mForegroundCall = null;
+ }
+
+ if (mForegroundCall != oldForegroundCall) {
+ mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+ }
+ }
+
+ @NonNull
+ private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
+ return new CallAudioModeStateMachine.MessageArgs(
+ mActiveOrDialingCalls.size() > 0,
+ mRingingCalls.size() > 0,
+ mHoldingCalls.size() > 0,
+ mIsTonePlaying,
+ mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
+ Log.createSubsession());
+ }
+
+ private void playToneForDisconnectedCall(Call call) {
+ if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
+ Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
+ " and there is another call.");
+ return;
+ }
+
+ if (call.getDisconnectCause() != null) {
+ int toneToPlay = InCallTonePlayer.TONE_INVALID;
+
+ Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
+
+ switch(call.getDisconnectCause().getTone()) {
+ case ToneGenerator.TONE_SUP_BUSY:
+ toneToPlay = InCallTonePlayer.TONE_BUSY;
+ break;
+ case ToneGenerator.TONE_SUP_CONGESTION:
+ toneToPlay = InCallTonePlayer.TONE_CONGESTION;
+ break;
+ case ToneGenerator.TONE_CDMA_REORDER:
+ toneToPlay = InCallTonePlayer.TONE_REORDER;
+ break;
+ case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
+ toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
+ break;
+ case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
+ toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
+ break;
+ case ToneGenerator.TONE_SUP_ERROR:
+ toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
+ break;
+ case ToneGenerator.TONE_PROP_PROMPT:
+ toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
+ break;
+ }
+
+ Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
+
+ if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
+ mPlayerFactory.createPlayer(toneToPlay).startTone();
}
}
}
- private void requestAudioFocusAndSetMode(int stream, int mode) {
- Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s",
- streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream),
- modeToString(mode));
- Preconditions.checkState(stream != STREAM_NONE);
-
- // Even if we already have focus, if the stream is different we update audio manager to give
- // it a hint about the purpose of our focus.
- if (mAudioFocusStreamType != stream) {
- Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s",
- streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream));
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL,
- setArgs(stream)).sendToTarget();
- }
- mAudioFocusStreamType = stream;
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
- CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
-
- setMode(mode);
- }
-
- private void abandonAudioFocus() {
- if (hasFocus()) {
- setMode(AudioManager.MODE_NORMAL);
- Log.v(this, "abandoning audio focus");
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL,
- setArgs(0)).sendToTarget();
- mAudioFocusStreamType = STREAM_NONE;
- mCallToSpeedUpMTAudio = null;
- }
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
- CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.NO_FOCUS);
- }
-
- /**
- * Sets the audio mode.
- *
- * @param newMode Mode constant from AudioManager.MODE_*.
- */
- private void setMode(int newMode) {
- Preconditions.checkState(hasFocus());
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE,
- setArgs(newMode)).sendToTarget();
- }
-
- private void updateAudioForForegroundCall() {
- Call call = mCallsManager.getForegroundCall();
- if (call != null && call.getConnectionService() != null) {
- call.getConnectionService().onCallAudioStateChanged(call,
- mCallAudioRouteStateMachine.getCurrentCallAudioState());
+ private void playRingbackForCall(Call call) {
+ if (call == mForegroundCall && call.isRingbackRequested()) {
+ mRingbackPlayer.startRingbackForCall(call);
}
}
- /**
- * Returns the current foreground call in order to properly set the audio mode.
- */
- private Call getForegroundCall() {
- Call call = mCallsManager.getForegroundCall();
+ private void stopRingbackForCall(Call call) {
+ mRingbackPlayer.stopRingbackForCall(call);
+ }
- // We ignore any foreground call that is in the ringing state because we deal with ringing
- // calls exclusively through the mIsRinging variable set by {@link Ringer}.
- if (call != null && call.getState() == CallState.RINGING) {
- return null;
+ private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
+ for (Call call : calls) {
+ if (call != null) pw.println(call.getId());
}
-
- return call;
- }
-
- private boolean hasRingingForegroundCall() {
- Call call = mCallsManager.getForegroundCall();
- return call != null && call.getState() == CallState.RINGING;
- }
-
- private boolean hasFocus() {
- return mAudioFocusStreamType != STREAM_NONE;
- }
-
- /**
- * Translates an {@link AudioManager} stream type to a human-readable string description.
- *
- * @param streamType The stream type.
- * @return Human readable description.
- */
- private String streamTypeToString(int streamType) {
- switch (streamType) {
- case STREAM_NONE:
- return STREAM_DESCRIPTION_NONE;
- case AudioManager.STREAM_ALARM:
- return STREAM_DESCRIPTION_ALARM;
- case AudioManager.STREAM_BLUETOOTH_SCO:
- return STREAM_DESCRIPTION_BLUETOOTH_SCO;
- case AudioManager.STREAM_DTMF:
- return STREAM_DESCRIPTION_DTMF;
- case AudioManager.STREAM_MUSIC:
- return STREAM_DESCRIPTION_MUSIC;
- case AudioManager.STREAM_NOTIFICATION:
- return STREAM_DESCRIPTION_NOTIFICATION;
- case AudioManager.STREAM_RING:
- return STREAM_DESCRIPTION_RING;
- case AudioManager.STREAM_SYSTEM:
- return STREAM_DESCRIPTION_SYSTEM;
- case AudioManager.STREAM_VOICE_CALL:
- return STREAM_DESCRIPTION_VOICE_CALL;
- default:
- return "STEAM_OTHER_" + streamType;
- }
- }
-
- /**
- * Translates an {@link AudioManager} mode into a human readable string.
- *
- * @param mode The mode.
- * @return The string.
- */
- private String modeToString(int mode) {
- switch (mode) {
- case AudioManager.MODE_INVALID:
- return MODE_DESCRIPTION_INVALID;
- case AudioManager.MODE_CURRENT:
- return MODE_DESCRIPTION_CURRENT;
- case AudioManager.MODE_NORMAL:
- return MODE_DESCRIPTION_NORMAL;
- case AudioManager.MODE_RINGTONE:
- return MODE_DESCRIPTION_RINGTONE;
- case AudioManager.MODE_IN_CALL:
- return MODE_DESCRIPTION_IN_CALL;
- case AudioManager.MODE_IN_COMMUNICATION:
- return MODE_DESCRIPTION_IN_COMMUNICATION;
- default:
- return "MODE_OTHER_" + mode;
- }
- }
-
- /**
- * Dumps the state of the {@link CallAudioManager}.
- *
- * @param pw The {@code IndentingPrintWriter} to write the state to.
- */
- public void dump(IndentingPrintWriter pw) {
- pw.println("mAudioState: " + mCallAudioRouteStateMachine.getCurrentCallAudioState());
- pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType));
- pw.println("mIsRinging: " + mIsRinging);
- pw.println("mIsTonePlaying: " + mIsTonePlaying);
- pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode));
}
}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
new file mode 100644
index 0000000..a0b665f
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2015 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.server.telecom;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Message;
+import android.util.SparseArray;
+
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+public class CallAudioModeStateMachine extends StateMachine {
+ public static class MessageArgs {
+ public boolean hasActiveCalls;
+ public boolean hasRingingCalls;
+ public boolean hasHoldingCalls;
+ public boolean isTonePlaying;
+ public boolean foregroundCallIsVoip;
+ public Session session;
+
+ public MessageArgs(boolean hasActiveCalls, boolean hasRingingCalls, boolean hasHoldingCalls,
+ boolean isTonePlaying, boolean foregroundCallIsVoip, Session session) {
+ this.hasActiveCalls = hasActiveCalls;
+ this.hasRingingCalls = hasRingingCalls;
+ this.hasHoldingCalls = hasHoldingCalls;
+ this.isTonePlaying = isTonePlaying;
+ this.foregroundCallIsVoip = foregroundCallIsVoip;
+ this.session = session;
+ }
+
+ @Override
+ public String toString() {
+ return "MessageArgs{" +
+ "hasActiveCalls=" + hasActiveCalls +
+ ", hasRingingCalls=" + hasRingingCalls +
+ ", hasHoldingCalls=" + hasHoldingCalls +
+ ", isTonePlaying=" + isTonePlaying +
+ ", foregroundCallIsVoip=" + foregroundCallIsVoip +
+ ", session=" + session +
+ '}';
+ }
+ }
+
+ public static final int INITIALIZE = 1;
+ // These ENTER_*_FOCUS commands are for testing.
+ public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
+ public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
+ public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
+ public static final int ABANDON_FOCUS_FOR_TESTING = 5;
+
+ public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
+ public static final int NO_MORE_RINGING_CALLS = 1002;
+ public static final int NO_MORE_HOLDING_CALLS = 1003;
+
+ public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
+ public static final int NEW_RINGING_CALL = 2002;
+ public static final int NEW_HOLDING_CALL = 2003;
+ public static final int MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
+
+ public static final int TONE_STARTED_PLAYING = 3001;
+ public static final int TONE_STOPPED_PLAYING = 3002;
+
+ public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
+
+ private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+ put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
+ put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
+ put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
+ put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
+ put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
+ put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
+ put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
+ put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
+ put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
+ put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
+ put(MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, "MT_AUDIO_SPEEDUP_FOR_RINGING_CALL");
+ put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
+ put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
+ put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
+ }};
+
+ private class BaseState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case ENTER_CALL_FOCUS_FOR_TESTING:
+ transitionTo(mSimCallFocusState);
+ return HANDLED;
+ case ENTER_COMMS_FOCUS_FOR_TESTING:
+ transitionTo(mVoipCallFocusState);
+ return HANDLED;
+ case ENTER_RING_FOCUS_FOR_TESTING:
+ transitionTo(mRingingFocusState);
+ return HANDLED;
+ case ABANDON_FOCUS_FOR_TESTING:
+ transitionTo(mUnfocusedState);
+ return HANDLED;
+ case INITIALIZE:
+ mIsInitialized = true;
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private class UnfocusedState extends BaseState {
+ @Override
+ public void enter() {
+ if (mIsInitialized) {
+ Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
+ mAudioManager.abandonAudioFocusForCall();
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+ mMostRecentMode = AudioManager.MODE_NORMAL;
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_HOLDING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ Log.w(LOG_TAG, "Newly active/dialing call appeared from an unfocused state. " +
+ "Args are:\n" + args.toString());
+ transitionTo(args.foregroundCallIsVoip
+ ? mVoipCallFocusState : mSimCallFocusState);
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ transitionTo(mRingingFocusState);
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // This really shouldn't happen, but transition to the focused state anyway.
+ Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
+ " Args are: \n" + args.toString());
+ transitionTo(mOtherFocusState);
+ return HANDLED;
+ case TONE_STARTED_PLAYING:
+ // This shouldn't happen either, but perform the action anyway.
+ Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
+ + args.toString());
+ return HANDLED;
+ default:
+ // The forced focus switch commands are handled by BaseState.
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private class RingingFocusState extends BaseState {
+ @Override
+ public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering RINGING state");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
+ // Preserving behavior from the old CallAudioManager.
+ Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
+ + " Resetting to NORMAL first.");
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ }
+ mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+
+ mCallAudioManager.startRinging();
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ }
+
+ @Override
+ public void exit() {
+ // Audio mode and audio stream will be set by the next state.
+ mCallAudioManager.stopRinging();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+ // Do nothing. Loss of an active call should not impact ringer.
+ return HANDLED;
+ case NO_MORE_HOLDING_CALLS:
+ // Do nothing and keep ringing.
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // If there are active or holding calls, switch to the appropriate focus.
+ // Otherwise abandon focus.
+ if (args.hasActiveCalls) {
+ if (args.foregroundCallIsVoip) {
+ transitionTo(mVoipCallFocusState);
+ } else {
+ transitionTo(mSimCallFocusState);
+ }
+ } else if (args.hasHoldingCalls || args.isTonePlaying) {
+ transitionTo(mOtherFocusState);
+ } else {
+ transitionTo(mUnfocusedState);
+ }
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ // If a call becomes active suddenly, give it priority over ringing.
+ transitionTo(args.foregroundCallIsVoip
+ ? mVoipCallFocusState : mSimCallFocusState);
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
+ "ringing state.");
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // This really shouldn't happen, but transition to the focused state anyway.
+ Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
+ " Args are: " + args.toString());
+ transitionTo(mOtherFocusState);
+ return HANDLED;
+ case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
+ // This happens when an IMS call is answered by the in-call UI. Special case
+ // that we have to deal with for some reason.
+
+ // VOIP calls should never invoke this mechanism, so transition directly to
+ // the sim call focus state.
+ transitionTo(mSimCallFocusState);
+ return HANDLED;
+ default:
+ // The forced focus switch commands are handled by BaseState.
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private class SimCallFocusState extends BaseState {
+ @Override
+ public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ mMostRecentMode = AudioManager.MODE_IN_CALL;
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+ // Switch to either ringing, holding, or inactive
+ if (args.hasHoldingCalls || args.isTonePlaying) {
+ transitionTo(mOtherFocusState);
+ } else if (args.hasRingingCalls) {
+ transitionTo(mRingingFocusState);
+ } else {
+ transitionTo(mUnfocusedState);
+ }
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // Don't transition state, but stop any call-waiting tones that may have been
+ // playing.
+ mCallAudioManager.stopCallWaiting();
+ return HANDLED;
+ case NO_MORE_HOLDING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ // Do nothing. Already active.
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ // Don't make a call ring over an active call, but do play a call waiting tone.
+ mCallAudioManager.startCallWaiting();
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // Don't do anything now. Putting an active call on hold will be handled when
+ // NO_MORE_ACTIVE_CALLS is processed.
+ return HANDLED;
+ case FOREGROUND_VOIP_MODE_CHANGE:
+ if (args.foregroundCallIsVoip) {
+ transitionTo(mVoipCallFocusState);
+ }
+ return HANDLED;
+ default:
+ // The forced focus switch commands are handled by BaseState.
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private class VoipCallFocusState extends BaseState {
+ @Override
+ public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+ mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+ // Switch to either ringing, holding, or inactive
+ if (args.hasHoldingCalls || args.isTonePlaying) {
+ transitionTo(mOtherFocusState);
+ } else if (args.hasRingingCalls) {
+ transitionTo(mRingingFocusState);
+ } else {
+ transitionTo(mUnfocusedState);
+ }
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // Don't transition state, but stop any call-waiting tones that may have been
+ // playing.
+ mCallAudioManager.stopCallWaiting();
+ return HANDLED;
+ case NO_MORE_HOLDING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ // Do nothing. Already active.
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ // Don't make a call ring over an active call, but do play a call waiting tone.
+ mCallAudioManager.startCallWaiting();
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // Don't do anything now. Putting an active call on hold will be handled when
+ // NO_MORE_ACTIVE_CALLS is processed.
+ return HANDLED;
+ case FOREGROUND_VOIP_MODE_CHANGE:
+ if (!args.foregroundCallIsVoip) {
+ transitionTo(mSimCallFocusState);
+ }
+ return HANDLED;
+ default:
+ // The forced focus switch commands are handled by BaseState.
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /**
+ * This class is used for calls on hold and end-of-call tones.
+ */
+ private class OtherFocusState extends BaseState {
+ @Override
+ public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ mAudioManager.setMode(mMostRecentMode);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_HOLDING_CALLS:
+ if (args.hasActiveCalls) {
+ transitionTo(args.foregroundCallIsVoip
+ ? mVoipCallFocusState : mSimCallFocusState);
+ } else if (args.hasRingingCalls) {
+ transitionTo(mRingingFocusState);
+ } else if (!args.isTonePlaying) {
+ transitionTo(mUnfocusedState);
+ }
+ // Do nothing if a tone is playing.
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ transitionTo(args.foregroundCallIsVoip
+ ? mVoipCallFocusState : mSimCallFocusState);
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ // Apparently this is current behavior. Should this be the case?
+ transitionTo(mRingingFocusState);
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // If there are no more ringing calls in this state, then stop any call-waiting
+ // tones that may be playing.
+ mCallAudioManager.stopCallWaiting();
+ return HANDLED;
+ case TONE_STOPPED_PLAYING:
+ if (!args.hasActiveCalls && !args.hasRingingCalls && !args.hasHoldingCalls
+ && !args.isTonePlaying) {
+ transitionTo(mUnfocusedState);
+ }
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
+
+ private final State mUnfocusedState = new UnfocusedState();
+ private final State mRingingFocusState = new RingingFocusState();
+ private final State mSimCallFocusState = new SimCallFocusState();
+ private final State mVoipCallFocusState = new VoipCallFocusState();
+ private final State mOtherFocusState = new OtherFocusState();
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+ private CallAudioManager mCallAudioManager;
+
+ private int mMostRecentMode;
+ private boolean mIsInitialized = false;
+
+ public CallAudioModeStateMachine(Context context) {
+ super(CallAudioModeStateMachine.class.getSimpleName());
+ mContext = context;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mMostRecentMode = AudioManager.MODE_NORMAL;
+
+ addState(mUnfocusedState);
+ addState(mRingingFocusState);
+ addState(mSimCallFocusState);
+ addState(mVoipCallFocusState);
+ addState(mOtherFocusState);
+ setInitialState(mUnfocusedState);
+ start();
+ sendMessage(INITIALIZE);
+ }
+
+ public void setCallAudioManager(CallAudioManager callAudioManager) {
+ mCallAudioManager = callAudioManager;
+ }
+
+ @Override
+ protected void onPreHandleMessage(Message msg) {
+ if (msg.obj != null && msg.obj instanceof MessageArgs) {
+ Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
+ Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
+ } else {
+ Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
+ (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
+ Log.w(LOG_TAG, "The message was of code %d = %s",
+ msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
+ }
+ }
+
+ @Override
+ protected void onPostHandleMessage(Message msg) {
+ Log.endSession();
+ }
+
+ @Override
+ protected void unhandledMessage(Message msg) {
+ // TODO: figure out if anything actually needs to be done here.
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 32b4294..8a659e4 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -81,7 +81,6 @@
ConnectionServiceWrapper newService);
void onIncomingCallAnswered(Call call);
void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
- void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
void onRingbackRequested(Call call, boolean ringback);
void onIsConferencedChanged(Call call);
@@ -169,12 +168,6 @@
private boolean mCanAddCall = true;
- /**
- * The call the user is currently interacting with. This is the call that should have audio
- * focus and be visible in the in-call UI.
- */
- private Call mForegroundCall;
-
private Runnable mStopTone;
/**
@@ -192,7 +185,8 @@
InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
CallAudioManager.AudioServiceFactory audioServiceFactory,
BluetoothManager bluetoothManager,
- WiredHeadsetManager wiredHeadsetManager) {
+ WiredHeadsetManager wiredHeadsetManager,
+ SystemStateProvider systemStateProvider) {
mContext = context;
mLock = lock;
mContactsAsyncHelper = contactsAsyncHelper;
@@ -203,6 +197,8 @@
mWiredHeadsetManager = wiredHeadsetManager;
mBluetoothManager = bluetoothManager;
mDockManager = new DockManager(context);
+
+ mDtmfLocalTonePlayer = new DtmfLocalTonePlayer();
CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
context,
this,
@@ -221,25 +217,26 @@
wiredHeadsetManager,
mDockManager);
- mCallAudioManager = new CallAudioManager(context, mLock, this, callAudioRouteStateMachine);
-
- InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager,
+ InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
callAudioRoutePeripheralAdapter, lock);
+ SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
RingtoneFactory ringtoneFactory = new RingtoneFactory(context);
SystemVibrator systemVibrator = new SystemVibrator(context);
AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
- SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
- mRinger = new Ringer(
- mCallAudioManager, this, playerFactory, context, systemSettingsUtil,
- asyncRingtonePlayer, ringtoneFactory, systemVibrator);
+ mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
+ ringtoneFactory, systemVibrator);
+
+ mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
+ this, new CallAudioModeStateMachine(context), playerFactory,
+ mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
+
mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
mCallLogManager = new CallLogManager(context, phoneAccountRegistrar);
- mInCallController = new InCallController(context, mLock, this);
- mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
+ mInCallController = new InCallController(context, mLock, this, systemStateProvider);
mConnectionServiceRepository =
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
@@ -249,12 +246,8 @@
mListeners.add(mCallLogManager);
mListeners.add(mPhoneStateBroadcaster);
mListeners.add(mInCallController);
- mListeners.add(mRinger);
- mListeners.add(new RingbackPlayer(this, playerFactory));
- mListeners.add(new InCallToneMonitor(playerFactory, this));
mListeners.add(mCallAudioManager);
mListeners.add(missedCallNotifier);
- mListeners.add(mDtmfLocalTonePlayer);
mListeners.add(mHeadsetMediaButton);
mListeners.add(mProximitySensorManager);
@@ -308,7 +301,7 @@
public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
Log.d(this, "onSuccessfulIncomingCall");
- // Only set the incoming call as ringing if it isn't already disconnected. It is possible
+ // Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
if (incomingCall.getState() != CallState.DISCONNECTED &&
@@ -503,11 +496,15 @@
@VisibleForTesting
public Call getForegroundCall() {
- return mForegroundCall;
+ if (mCallAudioManager == null) {
+ // Happens when getForegroundCall is called before full initialization.
+ return null;
+ }
+ return mCallAudioManager.getForegroundCall();
}
- Ringer getRinger() {
- return mRinger;
+ CallAudioManager getCallAudioManager() {
+ return mCallAudioManager;
}
InCallController getInCallController() {
@@ -594,8 +591,8 @@
);
call.initAnalytics();
- if (mForegroundCall != null) {
- mForegroundCall.getAnalytics().setCallIsInterrupted(true);
+ if (getForegroundCall() != null) {
+ getForegroundCall().getAnalytics().setCallIsInterrupted(true);
call.getAnalytics().setCallIsAdditional(true);
}
@@ -708,8 +705,8 @@
initiatingUser);
Log.v(this, "startOutgoingCall found accounts = " + accounts);
- if (mForegroundCall != null) {
- Call ongoingCall = mForegroundCall;
+ if (getForegroundCall() != null) {
+ Call ongoingCall = getForegroundCall();
// If there is an ongoing call, use the same phone account to place this new call.
// If the ongoing call is a conference call, we fetch the phone account from the
// child calls because we don't have targetPhoneAccount set on Conference calls.
@@ -884,18 +881,19 @@
if (!mCalls.contains(call)) {
Log.i(this, "Request to answer a non-existent call %s", call);
} else {
+ Call foregroundCall = getForegroundCall();
// If the foreground call is not the ringing call and it is currently isActive() or
// STATE_DIALING, put it on hold before answering the call.
- if (mForegroundCall != null && mForegroundCall != call &&
- (mForegroundCall.isActive() ||
- mForegroundCall.getState() == CallState.DIALING)) {
- if (0 == (mForegroundCall.getConnectionCapabilities()
+ if (foregroundCall != null && foregroundCall != call &&
+ (foregroundCall.isActive() ||
+ foregroundCall.getState() == CallState.DIALING)) {
+ if (0 == (foregroundCall.getConnectionCapabilities()
& Connection.CAPABILITY_HOLD)) {
// This call does not support hold. If it is from a different connection
// service, then disconnect it, otherwise allow the connection service to
// figure out the right states.
- if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
- mForegroundCall.disconnect();
+ if (foregroundCall.getConnectionService() != call.getConnectionService()) {
+ foregroundCall.disconnect();
}
} else {
Call heldCall = getHeldCall();
@@ -906,8 +904,8 @@
}
Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
- mForegroundCall, call);
- mForegroundCall.hold();
+ foregroundCall, call);
+ foregroundCall.hold();
}
// TODO: Wait until we get confirmation of the active call being
// on-hold before answering the new call.
@@ -1188,8 +1186,9 @@
removeCall(call);
if (mLocallyDisconnectingCalls.contains(call)) {
mLocallyDisconnectingCalls.remove(call);
- if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
- mForegroundCall.unhold();
+ Call foregroundCall = getForegroundCall();
+ if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+ foregroundCall.unhold();
}
}
}
@@ -1336,8 +1335,9 @@
Call getFirstCallWithState(Call callToSkip, int... states) {
for (int currentState : states) {
// check the foreground first
- if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
- return mForegroundCall;
+ Call foregroundCall = getForegroundCall();
+ if (foregroundCall != null && foregroundCall.getState() == currentState) {
+ return foregroundCall;
}
for (Call call : mCalls) {
@@ -1536,53 +1536,6 @@
}
}
- /**
- * Checks which call should be visible to the user and have audio focus.
- */
- private void updateForegroundCall() {
- Trace.beginSection("updateForegroundCall");
- Call newForegroundCall = null;
- for (Call call : mCalls) {
- // TODO: Foreground-ness needs to be explicitly set. No call, regardless
- // of its state will be foreground by default and instead the connection service should
- // be notified when its calls enter and exit foreground state. Foreground will mean that
- // the call should play audio and listen to microphone if it wants.
-
- // Only top-level calls can be in foreground
- if (call.getParentCall() != null) {
- continue;
- }
-
- // Active calls have priority.
- if (call.isActive()) {
- newForegroundCall = call;
- break;
- }
-
- if (call.isAlive() || call.getState() == CallState.RINGING) {
- newForegroundCall = call;
- // Don't break in case there's an active call that has priority.
- }
- }
-
- if (newForegroundCall != mForegroundCall) {
- Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
- Call oldForegroundCall = mForegroundCall;
- mForegroundCall = newForegroundCall;
-
- for (CallsManagerListener listener : mListeners) {
- if (Log.SYSTRACE_DEBUG) {
- Trace.beginSection(listener.getClass().toString() + " updateForegroundCall");
- }
- listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
- if (Log.SYSTRACE_DEBUG) {
- Trace.endSection();
- }
- }
- }
- Trace.endSection();
- }
-
private void updateCanAddCall() {
boolean newCanAddCall = canAddCall();
if (newCanAddCall != mCanAddCall) {
@@ -1600,7 +1553,6 @@
}
private void updateCallsManagerState() {
- updateForegroundCall();
updateCanAddCall();
}
@@ -1885,7 +1837,6 @@
}
pw.decreaseIndent();
}
- pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
if (mCallAudioManager != null) {
pw.println("mCallAudioManager:");
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 58085a0..50716d5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -52,10 +52,6 @@
}
@Override
- public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
- }
-
- @Override
public void onCallAudioStateChanged(CallAudioState oldAudioState,
CallAudioState newAudioState) {
}
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index 1048e2b..27ffd28 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -40,10 +40,15 @@
private class DockBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
- int dockState = intent.getIntExtra(
- Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
- onDockChanged(dockState);
+ Log.startSession("DM.oR");
+ try {
+ if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+ int dockState = intent.getIntExtra(
+ Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ onDockChanged(dockState);
+ }
+ } finally {
+ Log.endSession();
}
}
}
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index a0d2862..20c9dd8 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,16 +34,13 @@
* class employs a concept of a call "session" that starts and stops when the foreground call
* changes.
*/
-class DtmfLocalTonePlayer extends CallsManagerListenerBase {
+class DtmfLocalTonePlayer {
/** Generator used to actually play the tone. */
private ToneGenerator mToneGenerator;
/** The current call associated with an existing dtmf session. */
private Call mCall;
- /** The context. */
- private final Context mContext;
-
/**
* Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator
* thread.
@@ -54,13 +51,8 @@
/** Handler running on the tonegenerator thread. */
private Handler mHandler;
+ public DtmfLocalTonePlayer() { }
- public DtmfLocalTonePlayer(Context context) {
- mContext = context;
- }
-
- /** {@inheritDoc} */
- @Override
public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
endDtmfSession(oldForegroundCall);
startDtmfSession(newForegroundCall);
@@ -219,7 +211,7 @@
};
}
- private static final int getMappedTone(char digit) {
+ private static int getMappedTone(char digit) {
if (digit >= '0' && digit <= '9') {
return ToneGenerator.TONE_DTMF_0 + digit - '0';
} else if (digit == '#') {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index df19474..26ff625 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -47,10 +47,12 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.telecom.IInCallService;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -136,6 +138,19 @@
}
};
+ private final SystemStateListener mSystemStateListener = new SystemStateListener() {
+ @Override
+ public void onCarModeChanged(boolean isCarMode) {
+ // Do something when the car mode changes.
+ }
+ };
+
+ private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
+ private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+ private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
+ private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
+ private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
+
/**
* Maintains a binding connection to the in-call app(s).
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
@@ -161,17 +176,21 @@
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
private final CallsManager mCallsManager;
+ private final SystemStateProvider mSystemStateProvider;
- public InCallController(
- Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
+ public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ SystemStateProvider systemStateProvider) {
mContext = context;
mLock = lock;
mCallsManager = callsManager;
- Resources resources = mContext.getResources();
+ mSystemStateProvider = systemStateProvider;
+ Resources resources = mContext.getResources();
mSystemInCallComponentName = new ComponentName(
resources.getString(R.string.ui_default_package),
resources.getString(R.string.incall_default_class));
+
+ mSystemStateProvider.addListener(mSystemStateListener);
}
@Override
@@ -330,82 +349,80 @@
* @param call The newly added call that triggered the binding to the in-call services.
*/
private void bindToServices(Call call) {
+ ComponentName inCallUIService = null;
+ ComponentName carModeInCallUIService = null;
+ List<ComponentName> nonUIInCallServices = new LinkedList<>();
+
+ // Loop through all the InCallService implementations that exist in the devices;
PackageManager packageManager = mContext.getPackageManager();
Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
-
- List<ComponentName> inCallControlServices = new ArrayList<>();
- ComponentName inCallUIService = null;
-
for (ResolveInfo entry :
packageManager.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA)) {
ServiceInfo serviceInfo = entry.serviceInfo;
if (serviceInfo != null) {
- boolean hasServiceBindPermission = serviceInfo.permission != null &&
- serviceInfo.permission.equals(
- Manifest.permission.BIND_INCALL_SERVICE);
- if (!hasServiceBindPermission) {
- Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
- serviceInfo.packageName);
- continue;
- }
+ ComponentName componentName =
+ new ComponentName(serviceInfo.packageName, serviceInfo.name);
- boolean hasControlInCallPermission = packageManager.checkPermission(
- Manifest.permission.CONTROL_INCALL_EXPERIENCE,
- serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
- boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
- DefaultDialerManager.getDefaultDialerApplication(mContext));
- if (!hasControlInCallPermission && !isDefaultDialerPackage) {
- Log.w(this, "Service does not have CONTROL_INCALL_EXPERIENCE permission: %s"
- + " and is not system or default dialer.", serviceInfo.packageName);
- continue;
- }
+ switch (getInCallServiceType(entry.serviceInfo, packageManager)) {
+ case IN_CALL_SERVICE_TYPE_DIALER_UI:
+ if (inCallUIService == null ||
+ inCallUIService.compareTo(componentName) > 0) {
+ inCallUIService = componentName;
+ }
+ break;
- boolean isUIService = serviceInfo.metaData != null &&
- serviceInfo.metaData.getBoolean(
- TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
- ComponentName componentName = new ComponentName(serviceInfo.packageName,
- serviceInfo.name);
- if (isUIService) {
- // For the main UI service, we always prefer the default dialer.
- if (isDefaultDialerPackage) {
- inCallUIService = componentName;
- Log.i(this, "Found default-dialer's In-Call UI: %s", componentName);
- }
- } else {
- // for non-UI services that have passed our checks, add them to the list of
- // service to bind to.
- inCallControlServices.add(componentName);
- }
+ case IN_CALL_SERVICE_TYPE_SYSTEM_UI:
+ // skip, will be added manually
+ break;
+ case IN_CALL_SERVICE_TYPE_CAR_MODE_UI:
+ if (carModeInCallUIService == null ||
+ carModeInCallUIService.compareTo(componentName) > 0) {
+ carModeInCallUIService = componentName;
+ }
+ break;
+
+ case IN_CALL_SERVICE_TYPE_NON_UI:
+ nonUIInCallServices.add(componentName);
+ break;
+
+ case IN_CALL_SERVICE_TYPE_INVALID:
+ break;
+
+ default:
+ Log.w(this, "unexpected in-call service type");
+ break;
+ }
}
}
- // Attempt to bind to the default-dialer InCallService first.
- if (inCallUIService != null) {
- // skip default dialer if we have an emergency call or if it failed binding.
- if (mCallsManager.hasEmergencyCall()) {
- Log.i(this, "Skipping default-dialer because of emergency call");
- inCallUIService = null;
- } else if (!bindToInCallService(inCallUIService, call, "def-dialer")) {
- Log.event(call, Log.Events.ERROR_LOG,
- "InCallService UI failed binding: " + inCallUIService);
- inCallUIService = null;
- }
- }
+ Log.i(this, "Car mode InCallService: %s", carModeInCallUIService);
+ Log.i(this, "Dialer InCallService: %s", inCallUIService);
- if (inCallUIService == null) {
- // We failed to connect to the default-dialer service, or none was provided. Switch to
- // the system built-in InCallService UI.
- inCallUIService = mSystemInCallComponentName;
- if (!bindToInCallService(inCallUIService, call, "system")) {
- Log.event(call, Log.Events.ERROR_LOG,
- "InCallService system UI failed binding: " + inCallUIService);
- }
+ // Adding the in-call services in order:
+ // (1) The carmode in-call if carmode is on.
+ // (2) The default-dialer in-call if not an emergency call
+ // (3) The system-provided in-call
+ List<ComponentName> orderedInCallUIServices = new LinkedList<>();
+ if (shouldUseCarModeUI() && carModeInCallUIService != null) {
+ orderedInCallUIServices.add(carModeInCallUIService);
+ }
+ if (!mCallsManager.hasEmergencyCall() && inCallUIService != null) {
+ orderedInCallUIServices.add(inCallUIService);
+ }
+ orderedInCallUIServices.add(mSystemInCallComponentName);
+
+ // TODO: Need to implement the fall-back logic in case the main UI in-call service rejects
+ // the binding request.
+ ComponentName inCallUIServiceToBind = orderedInCallUIServices.get(0);
+ if (!bindToInCallService(inCallUIServiceToBind, call, "ui")) {
+ Log.event(call, Log.Events.ERROR_LOG,
+ "InCallService system UI failed binding: " + inCallUIService);
}
mInCallUIComponentName = inCallUIService;
// Bind to the control InCallServices
- for (ComponentName componentName : inCallControlServices) {
+ for (ComponentName componentName : nonUIInCallServices) {
bindToInCallService(componentName, call, "control");
}
}
@@ -445,6 +462,68 @@
return false;
}
+ private boolean shouldUseCarModeUI() {
+ return mSystemStateProvider.isCarMode();
+ }
+
+ /**
+ * Returns the type of InCallService described by the specified serviceInfo.
+ */
+ private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
+ // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
+ // enforces that only Telecom can bind to it.
+ boolean hasServiceBindPermission = serviceInfo.permission != null &&
+ serviceInfo.permission.equals(
+ Manifest.permission.BIND_INCALL_SERVICE);
+ if (!hasServiceBindPermission) {
+ Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
+ serviceInfo.packageName);
+ return IN_CALL_SERVICE_TYPE_INVALID;
+ }
+
+ if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
+ mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
+ return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
+ }
+
+ // Check to see if the service is a car-mode UI type by checking that it has the
+ // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
+ // car-mode UI metadata.
+ boolean hasControlInCallPermission = packageManager.checkPermission(
+ Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+ serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
+ boolean isCarModeUIService = serviceInfo.metaData != null &&
+ serviceInfo.metaData.getBoolean(
+ TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
+ hasControlInCallPermission;
+ if (isCarModeUIService) {
+ return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+ }
+
+
+ // Check to see that it is the default dialer package
+ boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
+ DefaultDialerManager.getDefaultDialerApplication(mContext));
+ boolean isUIService = serviceInfo.metaData != null &&
+ serviceInfo.metaData.getBoolean(
+ TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
+ if (isDefaultDialerPackage && isUIService) {
+ return IN_CALL_SERVICE_TYPE_DIALER_UI;
+ }
+
+ // Also allow any in-call service that has the control-experience permission (to ensure
+ // that it is a system app) and doesn't claim to show any UI.
+ if (hasControlInCallPermission && !isUIService) {
+ return IN_CALL_SERVICE_TYPE_NON_UI;
+ }
+
+ // Anything else that remains, we will not bind to.
+ Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
+ serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
+ isCarModeUIService, isUIService);
+ return IN_CALL_SERVICE_TYPE_INVALID;
+ }
+
private void adjustServiceBindingsForEmergency() {
if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) {
// The connected UI is not the system UI, so lets check if we should switch them
diff --git a/src/com/android/server/telecom/InCallToneMonitor.java b/src/com/android/server/telecom/InCallToneMonitor.java
deleted file mode 100644
index afe0f06..0000000
--- a/src/com/android/server/telecom/InCallToneMonitor.java
+++ /dev/null
@@ -1,114 +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.server.telecom;
-
-import android.media.ToneGenerator;
-import android.telecom.Connection;
-import android.telecom.VideoProfile;
-
-import java.util.Collection;
-
-/**
- * Monitors events from CallsManager and plays in-call tones for events which require them, such as
- * different type of call disconnections (busy tone, congestion tone, etc).
- */
-public final class InCallToneMonitor extends CallsManagerListenerBase {
- private final InCallTonePlayer.Factory mPlayerFactory;
-
- private final CallsManager mCallsManager;
-
- InCallToneMonitor(InCallTonePlayer.Factory playerFactory, CallsManager callsManager) {
- mPlayerFactory = playerFactory;
- mCallsManager = callsManager;
- }
-
- @Override
- public void onCallStateChanged(Call call, int oldState, int newState) {
- if (mCallsManager.getForegroundCall() != call) {
- // We only play tones for foreground calls.
- return;
- }
-
- if (newState == CallState.DISCONNECTED && call.getDisconnectCause() != null) {
- int toneToPlay = InCallTonePlayer.TONE_INVALID;
-
- Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
-
- switch(call.getDisconnectCause().getTone()) {
- case ToneGenerator.TONE_SUP_BUSY:
- toneToPlay = InCallTonePlayer.TONE_BUSY;
- break;
- case ToneGenerator.TONE_SUP_CONGESTION:
- toneToPlay = InCallTonePlayer.TONE_CONGESTION;
- break;
- case ToneGenerator.TONE_CDMA_REORDER:
- toneToPlay = InCallTonePlayer.TONE_REORDER;
- break;
- case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
- toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
- break;
- case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
- toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
- break;
- case ToneGenerator.TONE_SUP_ERROR:
- toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
- break;
- case ToneGenerator.TONE_PROP_PROMPT:
- toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
- break;
- }
-
- Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
-
- if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
- mPlayerFactory.createPlayer(toneToPlay).startTone();
- }
- }
- }
-
- /**
- * Handles requests received via the {@link VideoProviderProxy} requesting a change in the video
- * state of the call by the peer. If the request involves the peer turning their camera on,
- * the call waiting tone is played to inform the user of the incoming request.
- *
- * @param call The call.
- * @param videoProfile The requested video profile.
- */
- @Override
- public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
- if (videoProfile == null) {
- return;
- }
-
- if (mCallsManager.getForegroundCall() != call) {
- // We only play tones for foreground calls.
- return;
- }
-
- int previousVideoState = call.getVideoState();
- int newVideoState = videoProfile.getVideoState();
- Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
- .videoStateToString(newVideoState));
-
- boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
- VideoProfile.isReceptionEnabled(newVideoState);
-
- if (isUpgradeRequest) {
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
- }
- }
-}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 4fa4389..77406d3 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -34,18 +34,20 @@
* Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
*/
public static class Factory {
- private final CallAudioManager mCallAudioManager;
+ private CallAudioManager mCallAudioManager;
private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
private final TelecomSystem.SyncRoot mLock;
- Factory(CallAudioManager callAudioManager,
- CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+ Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
TelecomSystem.SyncRoot lock) {
- mCallAudioManager = callAudioManager;
mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
mLock = lock;
}
+ public void setCallAudioManager(CallAudioManager callAudioManager) {
+ mCallAudioManager = callAudioManager;
+ }
+
public InCallTonePlayer createPlayer(int tone) {
return new InCallTonePlayer(tone, mCallAudioManager,
mCallAudioRoutePeripheralAdapter, mLock);
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 57ae24b..54329c8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -43,26 +43,20 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
- if ((newState == CallState.DIALING || newState == CallState.ACTIVE
- || newState == CallState.ON_HOLD) &&
- !mCallsManager.hasRingingCall()) {
- /*
- * EXTRA_STATE_RINGING takes precedence over EXTRA_STATE_OFFHOOK, so if there is
- * already a ringing call, don't broadcast EXTRA_STATE_OFFHOOK.
- */
- sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_OFFHOOK);
- }
+ updateStates(call);
}
@Override
public void onCallAdded(Call call) {
- if (call.getState() == CallState.RINGING) {
- sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_RINGING);
- }
- };
+ updateStates(call);
+ }
@Override
public void onCallRemoved(Call call) {
+ updateStates(call);
+ }
+
+ private void updateStates(Call call) {
// Recalculate the current phone state based on the consolidated state of the remaining
// calls in the call list.
int callState = TelephonyManager.CALL_STATE_IDLE;
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index bb2055f..af60b68 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -1,17 +1,17 @@
/*
- * Copyright 2014, The Android Open Source Project
+ * Copyright (C) 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
+ * 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.
+ * limitations under the License
*/
package com.android.server.telecom;
@@ -24,9 +24,7 @@
* able to turn off and on as the user switches between calls. This is why it is implemented as its
* own class.
*/
-class RingbackPlayer extends CallsManagerListenerBase {
-
- private final CallsManager mCallsManager;
+class RingbackPlayer {
private final InCallTonePlayer.Factory mPlayerFactory;
@@ -40,52 +38,16 @@
*/
private InCallTonePlayer mTonePlayer;
- RingbackPlayer(CallsManager callsManager, InCallTonePlayer.Factory playerFactory) {
- mCallsManager = callsManager;
+ RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
mPlayerFactory = playerFactory;
}
- /** {@inheritDoc} */
- @Override
- public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
- if (oldForegroundCall != null) {
- stopRingbackForCall(oldForegroundCall);
- }
-
- if (shouldStartRinging(newForegroundCall)) {
- startRingbackForCall(newForegroundCall);
- }
- }
-
- @Override
- public void onConnectionServiceChanged(
- Call call,
- ConnectionServiceWrapper oldService,
- ConnectionServiceWrapper newService) {
-
- // Treat as ending or begining dialing based on the state transition.
- if (shouldStartRinging(call)) {
- startRingbackForCall(call);
- } else if (newService == null) {
- stopRingbackForCall(call);
- }
- }
-
- @Override
- public void onRingbackRequested(Call call, boolean ignored) {
- if (shouldStartRinging(call)) {
- startRingbackForCall(call);
- } else {
- stopRingbackForCall(call);
- }
- }
-
/**
* Starts ringback for the specified dialing call as needed.
*
* @param call The call for which to ringback.
*/
- private void startRingbackForCall(Call call) {
+ public void startRingbackForCall(Call call) {
Preconditions.checkState(call.getState() == CallState.DIALING);
if (mCall == call) {
@@ -112,7 +74,7 @@
*
* @param call The call for which to stop ringback.
*/
- private void stopRingbackForCall(Call call) {
+ public void stopRingbackForCall(Call call) {
if (mCall == call) {
// The foreground call is no longer dialing or is no longer the foreground call. In
// either case, stop the ringback tone.
@@ -127,11 +89,4 @@
}
}
}
-
- private boolean shouldStartRinging(Call call) {
- return call != null
- && mCallsManager.getForegroundCall() == call
- && call.getState() == CallState.DIALING
- && call.isRingbackRequested();
- }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index da22e98..0b96e99 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -11,7 +11,7 @@
* 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.
+ * limitations under the License
*/
package com.android.server.telecom;
@@ -27,25 +27,17 @@
import com.android.internal.annotations.VisibleForTesting;
-import java.util.LinkedList;
-import java.util.List;
-
/**
* Controls the ringtone player.
- * TODO: Turn this into a proper state machine: Ringing, CallWaiting, Stopped.
*/
@VisibleForTesting
-public final class Ringer extends CallsManagerListenerBase {
+public final class Ringer {
private static final long[] VIBRATION_PATTERN = new long[] {
0, // No delay before starting
1000, // How long to vibrate
1000, // How long to wait before vibrating again
};
- private static final int STATE_RINGING = 1;
- private static final int STATE_CALL_WAITING = 2;
- private static final int STATE_STOPPED = 3;
-
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -58,21 +50,23 @@
* Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
* calls and explicit ordering is useful for maintaining the proper state of the ringer.
*/
- private final List<Call> mRingingCalls = new LinkedList<>();
private final SystemSettingsUtil mSystemSettingsUtil;
- private final CallAudioManager mCallAudioManager;
- private final CallsManager mCallsManager;
private final InCallTonePlayer.Factory mPlayerFactory;
private final AsyncRingtonePlayer mRingtonePlayer;
private final Context mContext;
private final Vibrator mVibrator;
- private int mState = STATE_STOPPED;
private InCallTonePlayer mCallWaitingPlayer;
private RingtoneFactory mRingtoneFactory;
/**
+ * Call objects that are ringing or call-waiting. These are used only for logging purposes.
+ */
+ private Call mRingingCall;
+ private Call mCallWaitingCall;
+
+ /**
* Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
*/
private boolean mIsVibrating = false;
@@ -80,8 +74,6 @@
/** Initializes the Ringer. */
@VisibleForTesting
public Ringer(
- CallAudioManager callAudioManager,
- CallsManager callsManager,
InCallTonePlayer.Factory playerFactory,
Context context,
SystemSettingsUtil systemSettingsUtil,
@@ -90,8 +82,6 @@
Vibrator vibrator) {
mSystemSettingsUtil = systemSettingsUtil;
- mCallAudioManager = callAudioManager;
- mCallsManager = callsManager;
mPlayerFactory = playerFactory;
mContext = context;
// We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
@@ -101,156 +91,81 @@
mRingtoneFactory = ringtoneFactory;
}
- @Override
- public void onCallAdded(final Call call) {
- if (call.isIncoming() && call.getState() == CallState.RINGING) {
- if (mRingingCalls.contains(call)) {
- Log.wtf(this, "New ringing call is already in list of unanswered calls");
- }
- mRingingCalls.add(call);
- updateRinging(call);
- }
- }
-
- @Override
- public void onCallRemoved(Call call) {
- removeFromUnansweredCall(call);
- }
-
- @Override
- public void onCallStateChanged(Call call, int oldState, int newState) {
- if (newState != CallState.RINGING) {
- removeFromUnansweredCall(call);
- }
- }
-
- @Override
- public void onIncomingCallAnswered(Call call) {
- onRespondedToIncomingCall(call);
- }
-
- @Override
- public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
- onRespondedToIncomingCall(call);
- }
-
- @Override
- public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
- Call ringingCall = null;
- if (mRingingCalls.contains(newForegroundCall)) {
- ringingCall = newForegroundCall;
- } else if (mRingingCalls.contains(oldForegroundCall)) {
- ringingCall = oldForegroundCall;
- }
- if (ringingCall != null) {
- updateRinging(ringingCall);
- }
- }
-
- /**
- * Silences the ringer for any actively ringing calls.
- */
- void silence() {
- for (Call call : mRingingCalls) {
- call.silence();
- }
-
- // Remove all calls from the "ringing" set and then update the ringer.
- mRingingCalls.clear();
- updateRinging(null);
- }
-
- private void onRespondedToIncomingCall(Call call) {
- // Only stop the ringer if this call is the top-most incoming call.
- if (getTopMostUnansweredCall() == call) {
- removeFromUnansweredCall(call);
- }
- }
-
- private Call getTopMostUnansweredCall() {
- return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
- }
-
- /**
- * Removes the specified call from the list of unanswered incoming calls and updates the ringer
- * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
- * present in the list of incoming calls.
- */
- private void removeFromUnansweredCall(Call call) {
- mRingingCalls.remove(call);
- updateRinging(call);
- }
-
- private void updateRinging(Call call) {
- if (mRingingCalls.isEmpty()) {
- stopRinging(call, "No more ringing calls found");
- stopCallWaiting(call);
- } else {
- startRingingOrCallWaiting(call);
- }
- }
-
- private void startRingingOrCallWaiting(Call call) {
- Call foregroundCall = mCallsManager.getForegroundCall();
- Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
-
+ public void startRinging(Call foregroundCall) {
if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
return;
}
- if (mRingingCalls.contains(foregroundCall)) {
- // The foreground call is one of incoming calls so play the ringer out loud.
- stopCallWaiting(call);
+ stopCallWaiting();
- if (!shouldRingForContact(foregroundCall.getContactUri())) {
- return;
+ if (!shouldRingForContact(foregroundCall.getContactUri())) {
+ return;
+ }
+
+ AudioManager audioManager =
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+ mRingingCall = foregroundCall;
+ Log.event(foregroundCall, Log.Events.START_RINGER);
+ // Because we wait until a contact info query to complete before processing a
+ // call (for the purposes of direct-to-voicemail), the information about custom
+ // ringtones should be available by the time this code executes. We can safely
+ // request the custom ringtone from the call and expect it to be current.
+ mRingtonePlayer.play(
+ mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
+ } else {
+ Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
+ }
+
+ if (shouldVibrate(mContext) && !mIsVibrating) {
+ mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
+ VIBRATION_ATTRIBUTES);
+ mIsVibrating = true;
+ }
+ }
+
+ public void startCallWaiting(Call call) {
+ if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
+ return;
+ }
+
+ Log.v(this, "Playing call-waiting tone.");
+
+ stopRinging();
+
+ if (mCallWaitingPlayer == null) {
+ Log.event(call, Log.Events.START_CALL_WAITING_TONE);
+ mCallWaitingCall = call;
+ mCallWaitingPlayer =
+ mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ mCallWaitingPlayer.startTone();
+ }
+ }
+
+ public void stopRinging() {
+ if (mRingingCall != null) {
+ Log.event(mRingingCall, Log.Events.STOP_RINGER);
+ mRingingCall = null;
+ }
+
+ mRingtonePlayer.stop();
+
+ if (mIsVibrating) {
+ mVibrator.cancel();
+ mIsVibrating = false;
+ }
+ }
+
+ public void stopCallWaiting() {
+ Log.v(this, "stop call waiting.");
+ if (mCallWaitingPlayer != null) {
+ if (mCallWaitingCall != null) {
+ Log.event(mCallWaitingCall, Log.Events.STOP_CALL_WAITING_TONE);
+ mCallWaitingCall = null;
}
- AudioManager audioManager =
- (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
- if (mState != STATE_RINGING) {
- Log.event(call, Log.Events.START_RINGER);
- mState = STATE_RINGING;
- }
- mCallAudioManager.setIsRinging(call, true);
-
- // Because we wait until a contact info query to complete before processing a
- // call (for the purposes of direct-to-voicemail), the information about custom
- // ringtones should be available by the time this code executes. We can safely
- // request the custom ringtone from the call and expect it to be current.
- mRingtonePlayer.play(
- mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
- } else {
- Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
- }
-
- if (shouldVibrate(mContext) && !mIsVibrating) {
- mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
- VIBRATION_ATTRIBUTES);
- mIsVibrating = true;
- }
- } else if (foregroundCall != null) {
- // The first incoming call added to Telecom is not a foreground call at this point
- // in time. If the current foreground call is null at point, don't play call-waiting
- // as the call will eventually be promoted to the foreground call and play the
- // ring tone.
- Log.v(this, "Playing call-waiting tone.");
-
- // All incoming calls are in background so play call waiting.
- stopRinging(call, "Stop for call-waiting");
-
-
- if (mState != STATE_CALL_WAITING) {
- Log.event(call, Log.Events.START_CALL_WAITING_TONE);
- mState = STATE_CALL_WAITING;
- }
-
- if (mCallWaitingPlayer == null) {
- mCallWaitingPlayer =
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
- mCallWaitingPlayer.startTone();
- }
+ mCallWaitingPlayer.stopTone();
+ mCallWaitingPlayer = null;
}
}
@@ -264,37 +179,6 @@
return manager.matchesCallFilter(extras);
}
- private void stopRinging(Call call, String reasonTag) {
- if (mState == STATE_RINGING) {
- Log.event(call, Log.Events.STOP_RINGER, reasonTag);
- mState = STATE_STOPPED;
- }
-
- mRingtonePlayer.stop();
-
- if (mIsVibrating) {
- mVibrator.cancel();
- mIsVibrating = false;
- }
-
- // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
- // focus are voluntary so releasing focus too early is not detrimental.
- mCallAudioManager.setIsRinging(call, false);
- }
-
- private void stopCallWaiting(Call call) {
- Log.v(this, "stop call waiting.");
- if (mCallWaitingPlayer != null) {
- mCallWaitingPlayer.stopTone();
- mCallWaitingPlayer = null;
- }
-
- if (mState == STATE_CALL_WAITING) {
- Log.event(call, Log.Events.STOP_CALL_WAITING_TONE);
- mState = STATE_STOPPED;
- }
- }
-
private boolean shouldVibrate(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int ringerMode = audioManager.getRingerModeInternal();
diff --git a/src/com/android/server/telecom/Session.java b/src/com/android/server/telecom/Session.java
index 25a2189..51ef0fa 100644
--- a/src/com/android/server/telecom/Session.java
+++ b/src/com/android/server/telecom/Session.java
@@ -162,10 +162,15 @@
// Builds full session id recursively
private String getFullSessionId() {
- if(mParentSession == null) {
+ // Cache mParentSession locally to prevent a concurrency problem where
+ // Log.endParentSessions() is called while a logging statement is running (Log.i, for
+ // example) and setting mParentSession to null in a different thread after the null check
+ // occurred.
+ Session parentSession = mParentSession;
+ if(parentSession == null) {
return mSessionId;
} else {
- return mParentSession.getFullSessionId() + "_" + mSessionId;
+ return parentSession.getFullSessionId() + "_" + mSessionId;
}
}
diff --git a/src/com/android/server/telecom/SystemStateProvider.java b/src/com/android/server/telecom/SystemStateProvider.java
new file mode 100644
index 0000000..0b636cf
--- /dev/null
+++ b/src/com/android/server/telecom/SystemStateProvider.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 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.server.telecom;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Provides various system states to the rest of the telecom codebase. So far, that's only car-mode.
+ */
+public class SystemStateProvider {
+
+ public static interface SystemStateListener {
+ public void onCarModeChanged(boolean isCarMode);
+ }
+
+ private final Context mContext;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("SSP.oR");
+ try {
+ String action = intent.getAction();
+ if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
+ onEnterCarMode();
+ } else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
+ onExitCarMode();
+ } else {
+ Log.w(this, "Unexpected intent received: %s", intent.getAction());
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
+ private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
+ private boolean mIsCarMode;
+
+ public SystemStateProvider(Context context) {
+ mContext = context;
+
+ IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
+ intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ Log.i(this, "Registering car mode receiver: %s", intentFilter);
+
+ mIsCarMode = getSystemCarMode();
+ }
+
+ public void addListener(SystemStateListener listener) {
+ if (listener != null) {
+ mListeners.add(listener);
+ }
+ }
+
+ public boolean removeListener(SystemStateListener listener) {
+ return mListeners.remove(listener);
+ }
+
+ public boolean isCarMode() {
+ return mIsCarMode;
+ }
+
+ private void onEnterCarMode() {
+ if (!mIsCarMode) {
+ Log.i(this, "Entering carmode");
+ mIsCarMode = true;
+ notifyCarMode();
+ }
+ }
+
+ private void onExitCarMode() {
+ if (mIsCarMode) {
+ Log.i(this, "Exiting carmode");
+ mIsCarMode = false;
+ notifyCarMode();
+ }
+ }
+
+ private void notifyCarMode() {
+ for (SystemStateListener listener : mListeners) {
+ listener.onCarModeChanged(mIsCarMode);
+ }
+ }
+
+ /**
+ * Checks the system for the current car mode.
+ *
+ * @return True if in car mode, false otherwise.
+ */
+ private boolean getSystemCarMode() {
+ UiModeManager uiModeManager =
+ (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+
+ if (uiModeManager != null) {
+ return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index c635086..f91e086 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -568,7 +568,7 @@
long token = Binder.clearCallingIdentity();
try {
Log.i(this, "Silence Ringer requested by %s", callingPackage);
- mCallsManager.getRinger().silence();
+ mCallsManager.getCallAudioManager().silenceRingers();
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index d0528e8..702b59d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -104,19 +104,29 @@
private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- UserHandle currentUserHandle = new UserHandle(userHandleId);
- mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
- mCallsManager.onUserSwitch(currentUserHandle);
+ Log.startSession("TSSwR.oR");
+ try {
+ int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ UserHandle currentUserHandle = new UserHandle(userHandleId);
+ mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+ mCallsManager.onUserSwitch(currentUserHandle);
+ } finally {
+ Log.endSession();
+ }
}
};
private final BroadcastReceiver mUserStartingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- UserHandle addingUserHandle = new UserHandle(userHandleId);
- mCallsManager.onUserStarting(addingUserHandle);
+ Log.startSession("TSStR.oR");
+ try {
+ int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ UserHandle addingUserHandle = new UserHandle(userHandleId);
+ mCallsManager.onUserStarting(addingUserHandle);
+ } finally {
+ Log.endSession();
+ }
}
};
@@ -156,6 +166,7 @@
});
BluetoothManager bluetoothManager = new BluetoothManager(mContext);
WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
+ SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar);
@@ -172,7 +183,8 @@
inCallWakeLockControllerFactory,
audioServiceFactory,
bluetoothManager,
- wiredHeadsetManager);
+ wiredHeadsetManager,
+ systemStateProvider);
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index e0a9e9c..25284e4 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -112,15 +112,20 @@
private final class TtyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- Log.v(TtyManager.this, "onReceive, action: %s", action);
- if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
- int newPreferredTtyMode = intent.getIntExtra(
- TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
- if (mPreferredTtyMode != newPreferredTtyMode) {
- mPreferredTtyMode = newPreferredTtyMode;
- updateCurrentTtyMode();
+ Log.startSession("TBR.oR");
+ try {
+ String action = intent.getAction();
+ Log.v(TtyManager.this, "onReceive, action: %s", action);
+ if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
+ int newPreferredTtyMode = intent.getIntExtra(
+ TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+ if (mPreferredTtyMode != newPreferredTtyMode) {
+ mPreferredTtyMode = newPreferredTtyMode;
+ updateCurrentTtyMode();
+ }
}
+ } finally {
+ Log.endSession();
}
}
}
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index f25e928..253dfca 100644
--- a/src/com/android/server/telecom/WiredHeadsetManager.java
+++ b/src/com/android/server/telecom/WiredHeadsetManager.java
@@ -41,11 +41,16 @@
private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
- boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
- Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
- isPluggedIn);
- onHeadsetPluggedInChanged(isPluggedIn);
+ Log.startSession("WHBR.oR");
+ try {
+ if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
+ boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
+ Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
+ isPluggedIn);
+ onHeadsetPluggedInChanged(isPluggedIn);
+ }
+ } finally {
+ Log.endSession();
}
}
}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 878952e..8e4ae57 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -27,12 +27,12 @@
import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
-import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.InCallWakeLockController;
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 0672b6d..e645123 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -54,6 +54,7 @@
<service android:name="com.android.server.telecom.testapps.TestInCallServiceImpl"
android:process="com.android.server.telecom.testapps.TestInCallService"
android:permission="android.permission.BIND_INCALL_SERVICE" >
+ <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService"/>
</intent-filter>
@@ -68,6 +69,17 @@
</intent-filter>
</receiver>
+ <activity android:name="com.android.server.telecom.testapps.TestInCallUI"
+ android:process="com.android.server.telecom.testapps.TestInCallService"
+ android:label="@string/inCallUiAppLabel"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
<activity android:name="com.android.server.telecom.testapps.TestCallActivity"
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/testCallActivityLabel">
diff --git a/testapps/res/layout/call_list_item.xml b/testapps/res/layout/call_list_item.xml
new file mode 100644
index 0000000..c9f2ff7
--- /dev/null
+++ b/testapps/res/layout/call_list_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView
+ android:id="@+id/phoneNumber"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/callState"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/duration"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+</LinearLayout>
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
new file mode 100644
index 0000000..6a891e7
--- /dev/null
+++ b/testapps/res/layout/incall_screen.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <ListView
+ android:id="@+id/callListView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="#FFCC00"
+ android:dividerHeight="4px">
+ </ListView>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/end_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/endCallButton" />
+ <Button
+ android:id="@+id/mute_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/muteButton" />
+ <Button
+ android:id="@+id/hold_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/holdButton" >
+ </Button>
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 43c302d..599d5cc 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -37,4 +37,12 @@
<!-- String for button in TestDialerActivity that tries to exercise the
TelecomManager.cancelMissedCallNotifications() functionality -->
<string name="cancelMissedButton">Cancel missed calls</string>
+
+ <string name="endCallButton">End Call</string>
+
+ <string name="muteButton">Mute</string>
+
+ <string name="holdButton">Hold</string>
+
+ <string name="inCallUiAppLabel">Test InCall UI</string>
</resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
new file mode 100644
index 0000000..bea0e63
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.testapps;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+public class CallListAdapter extends BaseAdapter {
+ private static final String TAG = "CallListAdapter";
+
+ private final TestCallList.Listener mListener = new TestCallList.Listener() {
+ @Override
+ public void onCallAdded(Call call) {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ notifyDataSetChanged();
+ if (mCallList.size() == 0) {
+ mCallList.removeListener(this);
+ }
+ }
+ };
+
+ private final LayoutInflater mLayoutInflater;
+ private final TestCallList mCallList;
+ private final Handler mHandler = new Handler();
+ private final Runnable mSecondsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ if (mCallList.size() > 0) {
+ mHandler.postDelayed(this, 1000);
+ }
+ }
+ };
+
+ public CallListAdapter(Context context) {
+ mLayoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mCallList = TestCallList.getInstance();
+ mCallList.addListener(mListener);
+ mHandler.postDelayed(mSecondsRunnable, 1000);
+ }
+
+
+ @Override
+ public int getCount() {
+ Log.i(TAG, "size reporting: " + mCallList.size());
+ return mCallList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ Log.i(TAG, "getView: " + position);
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.call_list_item, parent, false);
+ }
+
+ TextView phoneNumber = (TextView) convertView.findViewById(R.id.phoneNumber);
+ TextView duration = (TextView) convertView.findViewById(R.id.duration);
+ TextView state = (TextView) convertView.findViewById(R.id.callState);
+
+ Call call = mCallList.getCall(position);
+ Uri handle = call.getDetails().getHandle();
+ phoneNumber.setText(handle == null ? "No number" : handle.getSchemeSpecificPart());
+
+ long durationMs = System.currentTimeMillis() - call.getDetails().getConnectTimeMillis();
+ duration.setText((durationMs / 1000) + " secs");
+
+ state.setText(getStateString(call));
+
+ Log.i(TAG, "Call found: " + handle.getSchemeSpecificPart() + ", " + durationMs);
+
+ return convertView;
+ }
+
+ private static String getStateString(Call call) {
+ switch (call.getState()) {
+ case Call.STATE_ACTIVE:
+ return "active";
+ case Call.STATE_CONNECTING:
+ return "connecting";
+ case Call.STATE_DIALING:
+ return "dialing";
+ case Call.STATE_DISCONNECTED:
+ return "disconnected";
+ case Call.STATE_DISCONNECTING:
+ return "disconnecting";
+ case Call.STATE_HOLDING:
+ return "on hold";
+ case Call.STATE_NEW:
+ return "new";
+ case Call.STATE_RINGING:
+ return "ringing";
+ case Call.STATE_SELECT_PHONE_ACCOUNT:
+ return "select phone account";
+ default:
+ return "unknown";
+ }
+ }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index a16c4e2..704c83d 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -24,6 +24,8 @@
import android.util.ArraySet;
import android.util.Log;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -31,6 +33,12 @@
* Maintains a list of calls received via the {@link TestInCallServiceImpl}.
*/
public class TestCallList extends Call.Listener {
+
+ public static abstract class Listener {
+ public void onCallAdded(Call call) {}
+ public void onCallRemoved(Call call) {}
+ }
+
private static final TestCallList INSTANCE = new TestCallList();
private static final String TAG = "TestCallList";
@@ -85,9 +93,10 @@
}
// The calls the call list knows about.
- private Set<Call> mCalls = new ArraySet<Call>();
+ private List<Call> mCalls = new LinkedList<Call>();
private Map<Call, TestVideoCallListener> mVideoCallListeners =
new ArrayMap<Call, TestVideoCallListener>();
+ private Set<Listener> mListeners = new ArraySet<Listener>();
/**
* Singleton accessor.
@@ -96,14 +105,32 @@
return INSTANCE;
}
+ public void addListener(Listener listener) {
+ if (listener != null) {
+ mListeners.add(listener);
+ }
+ }
+
+ public boolean removeListener(Listener listener) {
+ return mListeners.remove(listener);
+ }
+
+ public Call getCall(int position) {
+ return mCalls.get(position);
+ }
+
public void addCall(Call call) {
if (mCalls.contains(call)) {
Log.e(TAG, "addCall: Call already added.");
return;
}
- Log.v(TAG, "addCall: " + call + " " + System.identityHashCode(this));
+ Log.i(TAG, "addCall: " + call + " " + System.identityHashCode(this));
mCalls.add(call);
call.addListener(this);
+
+ for (Listener l : mListeners) {
+ l.onCallAdded(call);
+ }
}
public void removeCall(Call call) {
@@ -111,9 +138,13 @@
Log.e(TAG, "removeCall: Call cannot be removed -- doesn't exist.");
return;
}
- Log.v(TAG, "removeCall: " + call);
+ Log.i(TAG, "removeCall: " + call);
mCalls.remove(call);
call.removeListener(this);
+
+ for (Listener l : mListeners) {
+ l.onCallRemoved(call);
+ }
}
public void clearCalls() {
@@ -126,6 +157,10 @@
mVideoCallListeners.clear();
}
+ public int size() {
+ return mCalls.size();
+ }
+
/**
* For any video calls tracked, sends an upgrade to video request.
*/
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
index 68bbac9..03ca3d0 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.testapps;
+import android.content.Context;
+import android.content.Intent;
import android.telecom.Call;
import android.telecom.InCallService;
import android.telecom.Phone;
@@ -37,7 +39,12 @@
@Override
public void onCallAdded(Phone phone, Call call) {
Log.i(TAG, "onCallAdded: " + call.toString());
- TestCallList.getInstance().addCall(call);
+ TestCallList callList = TestCallList.getInstance();
+ callList.addCall(call);
+
+ if (callList.size() == 1) {
+ startInCallUI();
+ }
}
@Override
@@ -62,4 +69,11 @@
mPhone = null;
TestCallList.getInstance().clearCalls();
}
+
+ private void startInCallUI() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(this, TestInCallUI.class);
+ startActivity(intent);
+ }
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
new file mode 100644
index 0000000..ce53709
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.testapps;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ListView;
+
+public class TestInCallUI extends Activity {
+
+ private ListView mListView;
+ private TestCallList mCallList;
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.incall_screen);
+
+ mListView = (ListView) findViewById(R.id.callListView);
+ mListView.setAdapter(new CallListAdapter(this));
+ mListView.setVisibility(View.VISIBLE);
+
+ mCallList = TestCallList.getInstance();
+ mCallList.addListener(new TestCallList.Listener() {
+ @Override
+ public void onCallRemoved(Call call) {
+ if (mCallList.size() == 0) {
+ Log.i("Santos", "Ending the incall UI");
+ finish();
+ }
+ }
+ });
+
+ View endCallButton = findViewById(R.id.end_call_button);
+ View holdButton = findViewById(R.id.hold_button);
+ View muteButton = findViewById(R.id.mute_button);
+
+ endCallButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+ });
+ holdButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ if (call.getState() == Call.STATE_HOLDING) {
+ call.unhold();
+ } else {
+ call.hold();
+ }
+ }
+ }
+ });
+ muteButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ }
+ }
+ });
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4576690..f84a545 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -47,8 +47,8 @@
adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
To run a single test case:
- adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
- -e com.android.server.telecom.tests.unit.FooUnitTest
+ adb shell am instrument -w -e class com.android.server.telecom.tests.unit.FooUnitTest \
+ com.android.server.telecom.tests/android.test.InstrumentationTestRunner
-->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.server.telecom.tests"
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index ff69538..670959b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -23,10 +23,10 @@
import com.android.server.telecom.BluetoothManager;
import com.android.server.telecom.Call;
-import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.StatusBarNotifier;
import com.android.server.telecom.WiredHeadsetManager;
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index f5ead6c..e9cf022 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -40,7 +40,9 @@
import com.android.server.telecom.TelephonyUtil;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -48,6 +50,8 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.Arrays;
@@ -70,7 +74,7 @@
private static final String POST_DIAL_STRING = ";12345";
private static final String TEST_PHONE_ACCOUNT_ID= "testPhoneAccountId";
- private static final int TEST_TIMEOUT_MILLIS = 100;
+ private static final int TEST_TIMEOUT_MILLIS = 200;
private static final int CURRENT_USER_ID = 0;
private static final int OTHER_USER_ID = 10;
private static final int MANAGED_USER_ID = 11;
@@ -109,7 +113,15 @@
UserInfo managedProfileUserInfo = new UserInfo(OTHER_USER_ID, "test3",
UserInfo.FLAG_MANAGED_PROFILE);
+ doAnswer(new Answer<Uri>() {
+ @Override
+ public Uri answer(InvocationOnMock invocation) throws Throwable {
+ return (Uri) invocation.getArguments()[1];
+ }
+ }).when(mContentProvider).insert(anyString(), any(Uri.class), any(ContentValues.class));
+
when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
+ when(userManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class)))
.thenReturn(false);
when(userManager.getUsers(any(Boolean.class)))
@@ -549,8 +561,8 @@
ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
try {
Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
- verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS)).insert(any(String.class),
- eq(uri), captor.capture());
+ verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).atLeastOnce())
+ .insert(any(String.class), eq(uri), captor.capture());
} catch (android.os.RemoteException e) {
fail("Remote exception occurred during test execution");
}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index b6ce48a..7ac324e 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -182,6 +182,14 @@
}
@Override
+ public String getSystemServiceName(Class<?> svcClass) {
+ if (svcClass == UserManager.class) {
+ return Context.USER_SERVICE;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public int getUserId() {
return 0;
}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
deleted file mode 100644
index b8616ee..0000000
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2015 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.server.telecom.tests;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.net.Uri;
-import android.os.Vibrator;
-
-import com.android.server.telecom.AsyncRingtonePlayer;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.SystemSettingsUtil;
-import com.android.server.telecom.InCallTonePlayer;
-import com.android.server.telecom.Ringer;
-import com.android.server.telecom.RingtoneFactory;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RingerTest extends TelecomTestCase {
-
- @Mock Ringtone mMockRingtone;
- @Mock Vibrator mMockVibrator;
- @Mock RingtoneFactory mMockRingtoneFactory;
- @Mock SystemSettingsUtil mMockSystemSettings;
- @Mock CallsManager mMockCallsManager;
- @Mock CallAudioManager mMockCallAudioManager;
- @Mock InCallTonePlayer.Factory mMockToneFactory;
-
- private Ringer mRingerUnderTest;
-
- // These tests depend on an async handler to execute play() and stop() on the mock ringtone.
- // In order to verify these results, the test must wait an arbitrary amount of time to make sure
- // these methods are called.
- private static final int TEST_TIMEOUT = 100; //milliseconds
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- // Don't involve CallAudioManager logic in this unit test
- doNothing().when(mMockCallAudioManager).setIsRinging(any(Call.class), any(boolean.class));
- // Assume theatre mode is off for these tests
- when(mMockSystemSettings.isTheaterModeOn(any(Context.class))).thenReturn(false);
- // Assume the system is set to enable vibration when ringing
- when(mMockSystemSettings.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- mRingerUnderTest = new Ringer(
- mMockCallAudioManager, mMockCallsManager, mMockToneFactory,
- mComponentContextFixture.getTestDouble().getApplicationContext(),
- mMockSystemSettings, new AsyncRingtonePlayer(), mMockRingtoneFactory,
- mMockVibrator);
- }
-
- @Override
- public void tearDown() throws Exception {
- mRingerUnderTest = null;
- super.tearDown();
- }
-
- private Call createMockRingingCall() {
- Call mockCall = mock(Call.class);
- when(mockCall.isIncoming()).thenReturn(true);
- when(mockCall.getState()).thenReturn(CallState.RINGING);
- return mockCall;
- }
-
- public void testPlayRingtoneOnIncomingCall() throws Exception {
- Call mockCall = createMockRingingCall();
- when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
- mRingerUnderTest.onCallAdded(mockCall);
-
- verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
- any(AudioAttributes.class));
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
- }
-
- public void testCallAnsweredOnIncomingCall() throws Exception {
- Call mockCall = createMockRingingCall();
- when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
- // Make sure the ringtone plays for foreground call
- mRingerUnderTest.onCallAdded(mockCall);
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
- verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
- any(AudioAttributes.class));
-
- // Answer Call
- mRingerUnderTest.onIncomingCallAnswered(mockCall);
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
- verify(mMockVibrator).cancel();
- }
-
- public void testCallWaitingOnBackgroundCall() throws Exception {
- Call mockForegroundCall = mock(Call.class);
- Call mockBackgroundCall = createMockRingingCall();
- // Set foreground call to already answered
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
- InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
- when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
-
- // Add new call waiting call
- mRingerUnderTest.onCallAdded(mockBackgroundCall);
-
- verify(mockInCallTonePlayer).startTone();
- }
-
- public void testCallWaitingOnBackgroundCallDisconnected() throws Exception {
- Call mockForegroundCall = mock(Call.class);
- Call mockBackgroundCall = createMockRingingCall();
- // Set foreground call to already answered
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
- InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
- when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
-
- // Add new call waiting call
- mRingerUnderTest.onCallAdded(mockBackgroundCall);
-
- verify(mockInCallTonePlayer).startTone();
-
- // Reject the call waiting call
- mRingerUnderTest.onIncomingCallRejected(mockBackgroundCall, false, "");
-
- verify(mockInCallTonePlayer).stopTone();
- }
-
- public void testCallDisconnectedWhileRinging() throws Exception {
- Call mockCall = createMockRingingCall();
- when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
- // Make sure the ringtone plays for foreground call
- mRingerUnderTest.onCallAdded(mockCall);
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
- verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
- any(AudioAttributes.class));
-
- // Call Disconnected
- mRingerUnderTest.onCallRemoved(mockCall);
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
- verify(mMockVibrator).cancel();
- }
-
- public void testIncomingCallRejected() throws Exception {
- Call mockCall = createMockRingingCall();
- when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
- when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
- // Make sure the ringtone plays for foreground call
- mRingerUnderTest.onCallAdded(mockCall);
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
- verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
- any(AudioAttributes.class));
-
- // Answer Call
- mRingerUnderTest.onIncomingCallRejected(mockCall, false, "");
-
- verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
- verify(mMockVibrator).cancel();
- }
-}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
new file mode 100644
index 0000000..02e7ecf
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.tests;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for SystemStateProvider
+ */
+public class SystemStateProviderTest extends TelecomTestCase {
+
+ SystemStateProvider mSystemStateProvider;
+
+ @Mock Context mContext;
+ @Mock SystemStateListener mSystemStateListener;
+ @Mock UiModeManager mUiModeManager;
+ @Mock Intent mIntentEnter;
+ @Mock Intent mIntentExit;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testListeners() throws Exception {
+ SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+
+ assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+ systemStateProvider.addListener(mSystemStateListener);
+ assertTrue(systemStateProvider.removeListener(mSystemStateListener));
+ assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+ }
+
+ public void testQuerySystemForCarMode_True() {
+ when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ assertTrue(new SystemStateProvider(mContext).isCarMode());
+ }
+
+ public void testQuerySystemForCarMode_False() {
+ when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
+ assertFalse(new SystemStateProvider(mContext).isCarMode());
+ }
+
+ public void testReceiverAndIntentFilter() {
+ ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
+ new SystemStateProvider(mContext);
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+
+ assertEquals(2, intentFilter.getValue().countActions());
+ assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE, intentFilter.getValue().getAction(0));
+ assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE, intentFilter.getValue().getAction(1));
+ }
+
+ public void testOnEnterExitCarMode() {
+ ArgumentCaptor<BroadcastReceiver> receiver =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ new SystemStateProvider(mContext).addListener(mSystemStateListener);
+
+ verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+ when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
+ receiver.getValue().onReceive(mContext, mIntentEnter);
+ verify(mSystemStateListener).onCarModeChanged(true);
+
+ when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
+ receiver.getValue().onReceive(mContext, mIntentExit);
+ verify(mSystemStateListener).onCarModeChanged(false);
+
+ receiver.getValue().onReceive(mContext, new Intent("invalid action"));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index a6bbbe1..d3c3ff4 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -23,7 +23,6 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -40,16 +39,11 @@
import android.media.IAudioService;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Debug;
import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.telecom.Call;
-import android.telecom.CallAudioState;
-import android.telecom.Connection;
import android.telecom.ConnectionRequest;
-import android.telecom.DisconnectCause;
-import android.telecom.InCallService;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -57,11 +51,7 @@
import android.telecom.VideoProfile;
import com.android.internal.telecom.IInCallAdapter;
-import com.android.internal.telecom.IVideoProvider;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
-import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallIntentProcessor;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallsManagerListenerBase;
@@ -70,13 +60,12 @@
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
-import com.android.server.telecom.Log;
import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.components.PrimaryCallReceiver;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -85,16 +74,9 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.io.StringWriter;
-import java.util.Map;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-
/**
* Implements mocks and functionality required to implement telecom system tests.
*/
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index d6585d9..bc7f9e1 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.tests;
-import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.Log;
import org.mockito.ArgumentCaptor;