Merge "Fixes in responding call via sms"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7ace0e0..4caa4a3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -262,6 +262,7 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name=".testapps.IncomingSelfManagedCallActivity" />
<receiver android:name=".components.PrimaryCallReceiver"
android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 11b2d50..1cca867 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -175,4 +175,76 @@
<string name="handle_restricted">RESTRICTED</string>
<string name="toast_personal_call_msg">Using the personal dialer to make the call</string>
+
+ <!-- The "label" of the Notification for an incoming ringing call.
+ call_via refers to the name of the app the incoming call is coming from.
+ Example: Duo call from John Smith[CHAR LIMIT=60] -->
+ <string name="notification_incoming_call"><xliff:g id="call_via">%1$s</xliff:g> call from <xliff:g id="call_from">%2$s</xliff:g></string>
+
+ <!-- The "label" of the Notification for an incoming ringing video call.
+ call_via refers to the name of the app the incoming video call is coming from.
+ Example: Duo video call from John Smith[CHAR LIMIT=60] -->
+ <string name="notification_incoming_video_call"><xliff:g id="call_via">%1$s</xliff:g> video call from <xliff:g id="call_from">%2$s</xliff:g></string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause an ongoing call to be
+ disconnected. This message is the part of the notification that informs the user that
+ answering the current ringing call will cause a call from another app to be disconnected.
+ Example: Answering will end your Duo call [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_call">Answering will end your <xliff:g id="call_via">%1$s</xliff:g> call</string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause other ongoing calls
+ to be disconnected. This message is the part of the notification that informs the user
+ answering the current ringing call will cause calls from another app to be disconnected.
+ Example: Answering will end your Duo call [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_calls">Answering will end your <xliff:g id="call_via">%1$s</xliff:g> calls</string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause an ongoing video call
+ to be disconnected. This message is the part of the notification that informs the user
+ that answering the current ringing call will cause a video call from another app to be
+ disconnected.
+ Example: Answering will end your Duo video call [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_video_call">Answering will end your <xliff:g id="call_via">%1$s</xliff:g> video call</string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause an ongoing call
+ to be disconnected. This message is the part of the notification that informs the user
+ that answering the current ringing call will cause an ongoing call to be disconnected.
+ Similar to answering_ends_other_call, except does not specify which app.
+ [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_managed_call">Answering will end your ongoing call</string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause an ongoing call
+ to be disconnected. This message is the part of the notification that informs the user
+ that answering the current ringing call will cause an ongoing call to be disconnected.
+ Similar to answering_ends_other_call, except does not specify which app.
+ [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_managed_calls">Answering will end your ongoing calls</string>
+
+ <!-- The "details" of a Notification for an incoming call which will cause an ongoing video call
+ to be disconnected. This message is the part of the notification that informs the user
+ that answering the current ringing call will cause an ongoing video call to be
+ disconnected.
+ Similar to answering_ends_other_video_call, except does not specify which app.
+ [CHAR LIMIT=60] -->
+ <string name="answering_ends_other_managed_video_call">Answering will end your ongoing video call</string>
+
+ <!-- The "answer" button for an incoming call. [CHAR LIMIT=60] -->
+ <string name="answer_incoming_call">Answer</string>
+
+ <!-- The "decline" button for an incoming call. [CHAR LIMIT=60] -->
+ <string name="decline_incoming_call">Decline</string>
+
+ <!-- Error message shown to the user when an outgoing call cannot be placed due to an ongoing
+ phone call in a third-party app. For example:
+ Call cannot be placed due to your Duo call. [CHAR LIMIT=none] -->
+ <string name="cant_call_due_to_ongoing_call">Call cannot be placed due to your <xliff:g id="other_call">%1$s</xliff:g> call.</string>
+
+ <!-- Error message shown to the user when an outgoing call cannot be placed due to ongoing
+ phone calls in a third-party app. For example:
+ Call cannot be placed due to your Duo calls. [CHAR LIMIT=none] -->
+ <string name="cant_call_due_to_ongoing_calls">Call cannot be placed due to your <xliff:g id="other_call">%1$s</xliff:g> calls.</string>
+
+ <!-- Error message shown to the user when an outgoing call cannot be placed due to an ongoing
+ phone call in a third-party app. Unlike cant_call_due_to_ongoing_call, this is used when
+ the name of the other app is not known. [CHAR LIMIT=none] -->
+ <string name="cant_call_due_to_ongoing_unknown_call">Call cannot be placed due to a call in another app.</string>
</resources>
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
index 6884f5f..e6423a3 100644
--- a/scripts/telecom_testing.sh
+++ b/scripts/telecom_testing.sh
@@ -51,15 +51,15 @@
# Build and exit script early if build fails
if [ $coverage = true ] ; then
- emma_opt="EMMA_INSTRUMENT_STATIC=true"
+ emma_opt="EMMA_INSTRUMENT=true LOCAL_EMMA_INSTRUMENT=true EMMA_INSTRUMENT_STATIC=true"
else
- emma_opt="EMMA_INSTRUMENT_STATIC=false"
+ emma_opt="EMMA_INSTRUMENT=false"
fi
if [ $installwdep = true ] ; then
- mmma -j40 "packages/services/Telecomm/tests" ${emma_opt}
+ (export ${emma_opt}; mmma -j40 "packages/services/Telecomm/tests")
else
- mmm "packages/services/Telecomm/tests" ${emma_opt}
+ (export ${emma_opt}; mmm "packages/services/Telecomm/tests")
fi
if [ $? -ne 0 ] ; then
echo "Make failed! try using -a instead of -i if building with coverage"
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 2173fa5..54ff22b 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -615,6 +615,12 @@
}
}
}
+ if (conferenceCall.getState() == CallState.ON_HOLD &&
+ conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
+ // If the parent IMS CEP conference call is on hold, we should mark this call as
+ // being on hold regardless of what the other children are doing.
+ state = CALL_STATE_HELD;
+ }
} else if (isConferenceWithNoChildren) {
// Handle the special case of an IMS conference call without conference event package
// support. The call will be marked as a conference, but the conference will not have
@@ -630,7 +636,12 @@
} else {
addressUri = call.getHandle();
}
+
String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
+ if (address != null) {
+ address = PhoneNumberUtils.stripSeparators(address);
+ }
+
int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
if (shouldLog) {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index a786b55..ba89682 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -35,7 +35,6 @@
import android.telecom.Log;
import android.telecom.Logging.EventManager;
import android.telecom.ParcelableConnection;
-import android.telecom.ParcelableRttCall;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.Response;
@@ -87,6 +86,8 @@
private static final int RTT_PIPE_READ_SIDE_INDEX = 0;
private static final int RTT_PIPE_WRITE_SIDE_INDEX = 1;
+
+ private static final int INVALID_RTT_REQUEST_ID = -1;
/**
* Listener for events on the call.
*/
@@ -123,6 +124,8 @@
void onHoldToneRequested(Call call);
void onConnectionEvent(Call call, String event, Bundle extras);
void onExternalCallChanged(Call call, boolean isExternalCall);
+ void onRttInitiationFailure(Call call, int reason);
+ void onRemoteRttRequest(Call call, int requestId);
}
public abstract static class ListenerBase implements Listener {
@@ -184,13 +187,16 @@
public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
return false;
}
-
@Override
public void onHoldToneRequested(Call call) {}
@Override
public void onConnectionEvent(Call call, String event, Bundle extras) {}
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {}
+ @Override
+ public void onRttInitiationFailure(Call call, int reason) {}
+ @Override
+ public void onRemoteRttRequest(Call call, int requestId) {}
}
private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -283,6 +289,8 @@
private boolean mSpeakerphoneOn;
+ private boolean mIsDisconnectingChildCall = false;
+
/**
* Tracks the video states which were applicable over the duration of a call.
* See {@link VideoProfile} for a list of valid video states.
@@ -426,6 +434,11 @@
private int mRttMode;
/**
+ * Integer indicating the remote RTT request ID that is pending a response from the user.
+ */
+ private int mPendingRttRequestId = INVALID_RTT_REQUEST_ID;
+
+ /**
* Persists the specified parameters and initializes the new instance.
*
* @param context The context.
@@ -1111,7 +1124,7 @@
if (changedProperties != 0) {
int previousProperties = mConnectionProperties;
mConnectionProperties = connectionProperties;
- setIsRttCall((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
+ setRttStreams((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
Connection.PROPERTY_IS_RTT);
boolean didRttChange =
(changedProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT;
@@ -1168,6 +1181,19 @@
return mWasConferencePreviouslyMerged;
}
+ public boolean isDisconnectingChildCall() {
+ return mIsDisconnectingChildCall;
+ }
+
+ /**
+ * Sets whether this call is a child call.
+ */
+ private void maybeSetCallAsDisconnectingChild() {
+ if (mParentCall != null) {
+ mIsDisconnectingChildCall = true;
+ }
+ }
+
@VisibleForTesting
public Call getConferenceLevelActiveCall() {
return mConferenceLevelActiveCall;
@@ -1183,7 +1209,7 @@
*
* @return The {@link Context}.
*/
- Context getContext() {
+ public Context getContext() {
return mContext;
}
@@ -1391,6 +1417,7 @@
// Track that the call is now locally disconnecting.
setLocallyDisconnecting(true);
+ maybeSetCallAsDisconnectingChild();
if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
mState == CallState.CONNECTING) {
@@ -2027,7 +2054,22 @@
return mSpeakerphoneOn;
}
- public void setIsRttCall(boolean shouldBeRtt) {
+ public void stopRtt() {
+ if (mConnectionService != null) {
+ mConnectionService.stopRtt(this);
+ } else {
+ // If this gets called by the in-call app before the connection service is set, we'll
+ // just ignore it since it's really not supposed to happen.
+ Log.w(this, "stopRtt() called before connection service is set.");
+ }
+ }
+
+ public void sendRttRequest() {
+ setRttStreams(true);
+ mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
+ }
+
+ public void setRttStreams(boolean shouldBeRtt) {
boolean areStreamsInitialized = mInCallToConnectionServiceStreams != null
&& mConnectionServiceToInCallStreams != null;
if (shouldBeRtt && !areStreamsInitialized) {
@@ -2044,6 +2086,45 @@
}
}
+ public void onRttConnectionFailure(int reason) {
+ setRttStreams(false);
+ for (Listener l : mListeners) {
+ l.onRttInitiationFailure(this, reason);
+ }
+ }
+
+ public void onRemoteRttRequest() {
+ if (isRttCall()) {
+ Log.w(this, "Remote RTT request on a call that's already RTT");
+ return;
+ }
+
+ mPendingRttRequestId = mCallsManager.getNextRttRequestId();
+ for (Listener l : mListeners) {
+ l.onRemoteRttRequest(this, mPendingRttRequestId);
+ }
+ }
+
+ public void handleRttRequestResponse(int id, boolean accept) {
+ if (mPendingRttRequestId == INVALID_RTT_REQUEST_ID) {
+ Log.w(this, "Response received to a nonexistent RTT request: %d", id);
+ return;
+ }
+ if (id != mPendingRttRequestId) {
+ Log.w(this, "Response ID %d does not match expected %d", id, mPendingRttRequestId);
+ return;
+ }
+ setRttStreams(accept);
+ if (accept) {
+ Log.i(this, "RTT request %d accepted.", id);
+ mConnectionService.respondToRttRequest(
+ this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
+ } else {
+ Log.i(this, "RTT request %d rejected.", id);
+ mConnectionService.respondToRttRequest(this, null, null);
+ }
+ }
+
public void closeRttPipes() {
// TODO: may defer this until call is removed?
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index e50fd21..1075be2 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -477,6 +477,16 @@
pw.println("Foreground call:");
pw.println(mForegroundCall);
+
+ pw.println("CallAudioModeStateMachine pending messages:");
+ pw.increaseIndent();
+ mCallAudioModeStateMachine.dumpPendingMessages(pw);
+ pw.decreaseIndent();
+
+ pw.println("CallAudioRouteStateMachine pending messages:");
+ pw.increaseIndent();
+ mCallAudioRouteStateMachine.dumpPendingMessages(pw);
+ pw.decreaseIndent();
}
@VisibleForTesting
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 7121a53..3d778f2 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -24,6 +24,7 @@
import android.util.SparseArray;
import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -515,6 +516,10 @@
}
}
+ public void dumpPendingMessages(IndentingPrintWriter pw) {
+ getHandler().getLooper().dump(pw::println, "");
+ }
+
@Override
protected void onPostHandleMessage(Message msg) {
Log.endSession();
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 377ab48..7dfd78c 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -37,6 +37,7 @@
import android.util.SparseArray;
import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
@@ -65,6 +66,9 @@
* mIsMuted: a boolean indicating whether the audio is muted
*/
public class CallAudioRouteStateMachine extends StateMachine {
+ private static final String TELECOM_PACKAGE =
+ CallAudioRouteStateMachine.class.getPackage().getName();
+
/** Direct the audio stream through the device's earpiece. */
public static final int ROUTE_EARPIECE = CallAudioState.ROUTE_EARPIECE;
@@ -167,6 +171,19 @@
String action = intent.getAction();
if (action.equals(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)) {
+ // We get an this broadcast any time the notification filter is changed, even if
+ // we are the initiator of the change.
+ // So, we'll look at who the initiator of the manual zen rule is in the
+ // notification manager. If its us, then we can just exit now.
+ String initiator =
+ mInterruptionFilterProxy.getInterruptionModeInitiator();
+
+ if (TELECOM_PACKAGE.equals(initiator)) {
+ // We are the initiator of this change, so ignore it.
+ Log.i(this, "interruptionFilterChanged - ignoring own change");
+ return;
+ }
+
if (mAreNotificationSuppressed) {
// If we've already set the interruption filter, and the user changes it to
// something other than INTERRUPTION_FILTER_ALARMS, assume we will no longer
@@ -174,6 +191,8 @@
mAreNotificationSuppressed =
mInterruptionFilterProxy.getCurrentInterruptionFilter()
== NotificationManager.INTERRUPTION_FILTER_ALARMS;
+ Log.i(this, "interruptionFilterChanged - changing to %b",
+ mAreNotificationSuppressed);
}
}
} finally {
@@ -301,7 +320,7 @@
}
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
mAvailableRoutes);
- setSystemAudioState(newState);
+ setSystemAudioState(newState, true);
updateInternalCallAudioState();
}
@@ -489,7 +508,7 @@
setBluetoothOn(false);
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
mAvailableRoutes);
- setSystemAudioState(newState);
+ setSystemAudioState(newState, true);
updateInternalCallAudioState();
}
@@ -671,7 +690,7 @@
setBluetoothOn(true);
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
mAvailableRoutes);
- setSystemAudioState(newState);
+ setSystemAudioState(newState, true);
updateInternalCallAudioState();
}
@@ -805,8 +824,8 @@
}
return HANDLED;
case BT_AUDIO_DISCONNECT:
- // Ignore BT_AUDIO_DISCONNECT when ringing, since SCO audio should not be
- // connected.
+ // BT SCO might be connected when in-band ringing is enabled
+ sendInternalMessage(SWITCH_BASELINE_ROUTE);
return HANDLED;
default:
return NOT_HANDLED;
@@ -1300,6 +1319,10 @@
quitNow();
}
+ public void dumpPendingMessages(IndentingPrintWriter pw) {
+ getHandler().getLooper().dump(pw::println, "");
+ }
+
/**
* Sets whether notifications should be suppressed or not. Used when in a call to ensure the
* device will not vibrate due to notifications.
@@ -1529,7 +1552,15 @@
}
private int calculateBaselineRouteMessage(boolean isExplicitUserRequest) {
- if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ boolean isSkipEarpiece = false;
+ if (!isExplicitUserRequest) {
+ synchronized (mLock) {
+ // Check video calls to skip earpiece since the baseline for video
+ // calls should be the speakerphone route
+ isSkipEarpiece = mCallsManager.hasVideoCall();
+ }
+ }
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
} else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index c30641b..ced282c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -51,7 +51,6 @@
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.AsyncEmergencyContactNotifier;
@@ -69,7 +68,6 @@
import com.android.server.telecom.components.ErrorDialogActivity;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -177,6 +175,7 @@
*/
private int mCallId = 0;
+ private int mRttRequestId = 0;
/**
* Stores the current foreground user.
*/
@@ -261,7 +260,8 @@
mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
mContactsAsyncHelper, mLock);
- mDtmfLocalTonePlayer = new DtmfLocalTonePlayer();
+ mDtmfLocalTonePlayer =
+ new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
@@ -648,7 +648,7 @@
return true;
}
- boolean hasVideoCall() {
+ public boolean hasVideoCall() {
for (Call call : mCalls) {
if (VideoProfile.isVideo(call.getVideoState())) {
return true;
@@ -723,7 +723,7 @@
if (extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
if (phoneAccount != null &&
phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.setIsRttCall(true);
+ call.setRttStreams(true);
}
}
@@ -799,6 +799,7 @@
reusedCall = pendingCall;
} else {
Log.i(this, "Not reusing disconnected call %s", pendingCall);
+ callIter.remove();
pendingCall.disconnect();
}
}
@@ -968,7 +969,7 @@
&& extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
if (accountToUse != null
&& accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.setIsRttCall(true);
+ call.setRttStreams(true);
}
}
}
@@ -1421,7 +1422,7 @@
mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
if (realPhoneAccount != null
&& realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.setIsRttCall(true);
+ call.setRttStreams(true);
}
}
@@ -1495,8 +1496,15 @@
removeCall(call);
Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
if (mLocallyDisconnectingCalls.contains(call)) {
+ boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
+ Log.v(this, "markCallAsRemoved: isDisconnectingChildCall = "
+ + isDisconnectingChildCall + "call -> %s", call);
mLocallyDisconnectingCalls.remove(call);
- if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+ // Auto-unhold the foreground call due to a locally disconnected call, except if the
+ // call which was disconnected is a member of a conference (don't want to auto un-hold
+ // the conference if we remove a member of the conference).
+ if (!isDisconnectingChildCall && foregroundCall != null
+ && foregroundCall.getState() == CallState.ON_HOLD) {
foregroundCall.unhold();
}
} else if (foregroundCall != null &&
@@ -2044,7 +2052,7 @@
}
/**
- * Given a {@link PhoneAccountHandle} determines if there are and managed calls.
+ * Determines if there are any managed calls.
* @return {@code true} if there are managed calls, {@code false} otherwise.
*/
public boolean hasManagedCalls() {
@@ -2052,6 +2060,17 @@
!call.isExternalCall()).count() > 0;
}
+ /**
+ * Determines if there are any ongoing managed calls.
+ * @return {@code true} if there are ongoing managed calls, {@code false} otherwise.
+ */
+ public boolean hasOngoingManagedCalls() {
+ return getNumCallsWithState(
+ false /* isSelfManaged */, null /* excludeCall */,
+ null /* phoneAccountHandle */,
+ LIVE_CALL_STATES) > 0;
+ }
+
private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
if (hasMaximumManagedLiveCalls(call)) {
// NOTE: If the amount of live calls changes beyond 1, this logic will probably
@@ -2262,6 +2281,12 @@
}
}
+ public int getNextRttRequestId() {
+ synchronized (mLock) {
+ return (++mRttRequestId);
+ }
+ }
+
/**
* Callback when foreground user is switched. We will reload missed call in all profiles
* including the user itself. There may be chances that profiles are not started yet.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index be54f4f..7bb747e 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,6 +23,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecom.CallAudioState;
@@ -771,6 +772,54 @@
Log.endSession();
}
}
+
+ @Override
+ public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
+ throws RemoteException {
+ Log.startSession(sessionInfo, "CSW.oRIF");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onRttConnectionFailure(reason);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
+ throws RemoteException {
+ Log.startSession(sessionInfo, "CSW.oRRR");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onRemoteRttRequest();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
}
private final Adapter mAdapter = new Adapter();
@@ -860,7 +909,8 @@
gatewayInfo.getOriginalAddress());
}
- Log.addEvent(call, LogUtils.Events.START_CONNECTION, Log.piiHandle(call.getHandle()));
+ Log.addEvent(call, LogUtils.Events.START_CONNECTION,
+ Log.piiHandle(call.getHandle()));
// For self-managed incoming calls, if there is another ongoing call Telecom is
// responsible for showing a UI to ask the user if they'd like to answer this
@@ -872,7 +922,7 @@
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(call.getTargetPhoneAccount())
.setAddress(call.getHandle())
- .setExtras(call.getIntentExtras())
+ .setExtras(extras)
.setVideoState(call.getVideoState())
.setTelecomCallId(callId)
.setShouldShowIncomingCallUi(shouldShowIncomingCallUI)
@@ -923,7 +973,9 @@
Log.piiHandle(call.getHandle()));
try {
logOutgoing("createConnectionFailed %s", callId);
- mServiceInterface.createConnectionFailed(callId,
+ mServiceInterface.createConnectionFailed(
+ call.getConnectionManagerPhoneAccount(),
+ callId,
new ConnectionRequest(
call.getTargetPhoneAccount(),
call.getHandle(),
@@ -1212,6 +1264,41 @@
}
}
+ void startRtt(Call call, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("startRtt")) {
+ try {
+ logOutgoing("startRtt: %s %s %s", callId, fromInCall, toInCall);
+ mServiceInterface.startRtt(callId, fromInCall, toInCall, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ void stopRtt(Call call) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("stopRtt")) {
+ try {
+ logOutgoing("stopRtt: %s", callId);
+ mServiceInterface.stopRtt(callId, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ void respondToRttRequest(
+ Call call, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("respondToRttRequest")) {
+ try {
+ logOutgoing("respondToRttRequest: %s %s %s", callId, fromInCall, toInCall);
+ mServiceInterface.respondToRttUpgradeRequest(
+ callId, fromInCall, toInCall, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
/** {@inheritDoc} */
@Override
protected void setServiceInterface(IBinder binder) {
@@ -1272,12 +1359,12 @@
private void logIncoming(String msg, Object... params) {
Log.d(this, "ConnectionService -> Telecom[" + mComponentName.flattenToShortString() + "]: "
- + msg, params);
+ + msg, Log.pii(params));
}
private void logOutgoing(String msg, Object... params) {
Log.d(this, "Telecom -> ConnectionService[" + mComponentName.flattenToShortString() + "]: "
- + msg, params);
+ + msg, Log.pii(params));
}
private void queryRemoteConnectionServices(final UserHandle userHandle,
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index 5abbf49..10551a4 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -21,10 +21,13 @@
import android.media.ToneGenerator;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.telecom.Log;
+import android.telecom.Logging.Session;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -36,23 +39,108 @@
* changes.
*/
public class DtmfLocalTonePlayer {
- /** Generator used to actually play the tone. */
- private ToneGenerator mToneGenerator;
+ public static class ToneGeneratorProxy {
+ /** Generator used to actually play the tone. */
+ private ToneGenerator mToneGenerator;
+
+ public void create() {
+ if (mToneGenerator == null) {
+ try {
+ mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
+ } catch (RuntimeException e) {
+ Log.e(this, e, "Error creating local tone generator.");
+ mToneGenerator = null;
+ }
+ }
+ }
+
+ public void release() {
+ if (mToneGenerator != null) {
+ mToneGenerator.release();
+ mToneGenerator = null;
+ }
+ }
+
+ public boolean isPresent() {
+ return mToneGenerator != null;
+ }
+
+ public void startTone(int toneType, int durationMs) {
+ if (mToneGenerator != null) {
+ mToneGenerator.startTone(toneType, durationMs);
+ }
+ }
+
+ public void stopTone() {
+ if (mToneGenerator != null) {
+ mToneGenerator.stopTone();
+ }
+ }
+ }
+
+ private final class ToneHandler extends Handler {
+
+ public ToneHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.obj instanceof Session) {
+ Log.continueSession((Session) msg.obj, "DLTP.TH");
+ }
+
+ switch(msg.what) {
+ case EVENT_START_SESSION:
+ mToneGeneratorProxy.create();
+ break;
+ case EVENT_END_SESSION:
+ mToneGeneratorProxy.release();
+ break;
+ case EVENT_PLAY_TONE:
+ char c = (char) msg.arg1;
+ if (!mToneGeneratorProxy.isPresent()) {
+ Log.d(this, "playTone: no tone generator, %c.", c);
+ } else {
+ Log.d(this, "starting local tone: %c.", c);
+ int tone = getMappedTone(c);
+ if (tone != ToneGenerator.TONE_UNKNOWN) {
+ mToneGeneratorProxy.startTone(tone, -1 /* toneDuration */);
+ }
+ }
+ break;
+ case EVENT_STOP_TONE:
+ if (mToneGeneratorProxy.isPresent()) {
+ mToneGeneratorProxy.stopTone();
+ }
+ break;
+ default:
+ Log.w(this, "Unknown message: %d", msg.what);
+ break;
+ }
+ }
+ }
/** The current call associated with an existing dtmf session. */
private Call mCall;
/**
* Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator
- * thread.
+ * thread, as well as for actually playing the tones.
*/
- private static final int EVENT_CREATE_OBJECT = 1;
- private static final int EVENT_DELETE_OBJECT = 2;
+ private static final int EVENT_START_SESSION = 1;
+ private static final int EVENT_END_SESSION = 2;
+ private static final int EVENT_PLAY_TONE = 3;
+ private static final int EVENT_STOP_TONE = 4;
/** Handler running on the tonegenerator thread. */
- private Handler mHandler;
+ private ToneHandler mHandler;
- public DtmfLocalTonePlayer() { }
+ private final ToneGeneratorProxy mToneGeneratorProxy;
+
+ public DtmfLocalTonePlayer(ToneGeneratorProxy toneGeneratorProxy) {
+ mToneGeneratorProxy = toneGeneratorProxy;
+ }
public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
endDtmfSession(oldForegroundCall);
@@ -65,22 +153,14 @@
* @param call The associated call.
* @param c The digit to play.
*/
- void playTone(Call call, char c) {
+ public void playTone(Call call, char c) {
// Do nothing if it is not the right call.
if (mCall != call) {
return;
}
- synchronized(this) {
- if (mToneGenerator == null) {
- Log.d(this, "playTone: mToneGenerator == null, %c.", c);
- } else {
- Log.d(this, "starting local tone: %c.", c);
- int tone = getMappedTone(c);
- if (tone != ToneGenerator.TONE_UNKNOWN) {
- mToneGenerator.startTone(tone, -1 /* toneDuration */);
- }
- }
- }
+
+ getHandler().sendMessage(
+ getHandler().obtainMessage(EVENT_PLAY_TONE, (int) c, 0, Log.createSubsession()));
}
/**
@@ -88,19 +168,14 @@
*
* @param call The associated call.
*/
- void stopTone(Call call) {
+ public void stopTone(Call call) {
// Do nothing if it's not the right call.
if (mCall != call) {
return;
}
- synchronized(this) {
- if (mToneGenerator == null) {
- Log.d(this, "stopTone: mToneGenerator == null.");
- } else {
- Log.d(this, "stopping local tone.");
- mToneGenerator.stopTone();
- }
- }
+
+ getHandler().sendMessage(
+ getHandler().obtainMessage(EVENT_STOP_TONE, Log.createSubsession()));
}
/**
@@ -125,7 +200,8 @@
if (areLocalTonesEnabled) {
Log.d(this, "Posting create.");
- postMessage(EVENT_CREATE_OBJECT);
+ getHandler().sendMessage(
+ getHandler().obtainMessage(EVENT_START_SESSION, Log.createSubsession()));
}
}
@@ -141,75 +217,23 @@
mCall = null;
Log.d(this, "Posting delete.");
- postMessage(EVENT_DELETE_OBJECT);
+ getHandler().sendMessage(
+ getHandler().obtainMessage(EVENT_END_SESSION, Log.createSubsession()));
}
}
/**
- * Posts a message to the tonegenerator-thread handler. Creates the handler if the handler
- * has not been instantiated.
- *
- * @param messageCode The message to post.
+ * Creates a new ToneHandler on a separate thread if none exists, and returns it.
+ * No need for locking, since everything that calls this is protected by the Telecom lock.
*/
- private void postMessage(int messageCode) {
- synchronized(this) {
- if (mHandler == null) {
- mHandler = getNewHandler();
- }
-
- if (mHandler == null) {
- Log.d(this, "Message %d skipped because there is no handler.", messageCode);
- } else {
- mHandler.obtainMessage(messageCode, null).sendToTarget();
- }
+ @VisibleForTesting
+ public ToneHandler getHandler() {
+ if (mHandler == null) {
+ HandlerThread thread = new HandlerThread("tonegenerator-dtmf");
+ thread.start();
+ mHandler = new ToneHandler(thread.getLooper());
}
- }
-
- /**
- * Creates a new tonegenerator Handler running in its own thread.
- */
- private Handler getNewHandler() {
- Preconditions.checkState(mHandler == null);
-
- HandlerThread thread = new HandlerThread("tonegenerator-dtmf");
- thread.start();
-
- return new Handler(thread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case EVENT_CREATE_OBJECT:
- synchronized(DtmfLocalTonePlayer.this) {
- if (mToneGenerator == null) {
- try {
- mToneGenerator = new ToneGenerator(
- AudioManager.STREAM_DTMF, 80);
- } catch (RuntimeException e) {
- Log.e(this, e, "Error creating local tone generator.");
- mToneGenerator = null;
- }
- }
- }
- break;
- case EVENT_DELETE_OBJECT:
- synchronized(DtmfLocalTonePlayer.this) {
- if (mToneGenerator != null) {
- mToneGenerator.release();
- mToneGenerator = null;
- }
- // Delete the handler after the tone generator object is deleted by
- // the tonegenerator thread.
- if (mHandler != null && !mHandler.hasMessages(EVENT_CREATE_OBJECT)) {
- // Stop the handler only if there are no pending CREATE messages.
- mHandler.removeMessages(EVENT_DELETE_OBJECT);
- mHandler.getLooper().quitSafely();
- mHandler = null;
- }
- }
- break;
- }
- }
- };
+ return mHandler;
}
private static int getMappedTone(char digit) {
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 4b54760..0eeec44 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -496,13 +496,18 @@
}
@Override
- public void sendRttRequest() {
+ public void sendRttRequest(String callId) {
try {
Log.startSession("ICA.sRR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // TODO
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.sendRttRequest();
+ } else {
+ Log.w(this, "stopRtt(): call %s not found", callId);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -513,13 +518,18 @@
}
@Override
- public void respondToRttRequest(int id, boolean accept) {
+ public void respondToRttRequest(String callId, int id, boolean accept) {
try {
Log.startSession("ICA.rTRR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // TODO
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.handleRttRequestResponse(id, accept);
+ } else {
+ Log.w(this, "respondToRttRequest(): call %s not found", callId);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -530,13 +540,18 @@
}
@Override
- public void stopRtt() {
+ public void stopRtt(String callId) {
try {
Log.startSession("ICA.sRTT");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // TODO
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.stopRtt();
+ } else {
+ Log.w(this, "stopRtt(): call %s not found", callId);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -547,7 +562,7 @@
}
@Override
- public void setRttMode(int mode) {
+ public void setRttMode(String callId, int mode) {
try {
Log.startSession("ICA.sRM");
long token = Binder.clearCallingIdentity();
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index dce4364..e575097 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -29,7 +29,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Parcel;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -84,13 +83,16 @@
private class InCallServiceInfo {
private final ComponentName mComponentName;
private boolean mIsExternalCallsSupported;
+ private boolean mIsSelfManagedCallsSupported;
private final int mType;
public InCallServiceInfo(ComponentName componentName,
boolean isExternalCallsSupported,
+ boolean isSelfManageCallsSupported,
int type) {
mComponentName = componentName;
mIsExternalCallsSupported = isExternalCallsSupported;
+ mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
mType = type;
}
@@ -102,6 +104,10 @@
return mIsExternalCallsSupported;
}
+ public boolean isSelfManagedCallsSupported() {
+ return mIsSelfManagedCallsSupported;
+ }
+
public int getType() {
return mType;
}
@@ -120,18 +126,23 @@
if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
return false;
}
+ if (mIsSelfManagedCallsSupported != that.mIsSelfManagedCallsSupported) {
+ return false;
+ }
return mComponentName.equals(that.mComponentName);
}
@Override
public int hashCode() {
- return Objects.hash(mComponentName, mIsExternalCallsSupported);
+ return Objects.hash(mComponentName, mIsExternalCallsSupported,
+ mIsSelfManagedCallsSupported);
}
@Override
public String toString() {
- return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported + "]";
+ return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported +
+ " supportsSelfMg?" + mIsSelfManagedCallsSupported + "]";
}
}
@@ -599,6 +610,17 @@
public void onConnectionEvent(Call call, String event, Bundle extras) {
notifyConnectionEvent(call, event, extras);
}
+
+ @Override
+ public void onRttInitiationFailure(Call call, int reason) {
+ notifyRttInitiationFailure(call, reason);
+ updateCall(call, false, true);
+ }
+
+ @Override
+ public void onRemoteRttRequest(Call call, int requestId) {
+ notifyRemoteRttRequest(call, requestId);
+ }
};
private final SystemStateListener mSystemStateListener = new SystemStateListener() {
@@ -658,11 +680,6 @@
@Override
public void onCallAdded(Call call) {
- if (call.isSelfManaged()) {
- Log.i(this, "onCallAdded: skipped self-managed %s", call);
- return;
- }
-
if (!isBoundToServices()) {
bindToServices(call);
} else {
@@ -680,6 +697,10 @@
continue;
}
+ if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+ continue;
+ }
+
// Only send the RTT call if it's a UI in-call service
boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
@@ -700,11 +721,6 @@
@Override
public void onCallRemoved(Call call) {
- if (call.isSelfManaged()) {
- Log.i(this, "onCallRemoved: skipped self-managed %s", call);
- return;
- }
-
Log.i(this, "onCallRemoved: %s", call);
if (mCallsManager.getCalls().isEmpty()) {
/** Let's add a 2 second delay before we send unbind to the services to hopefully
@@ -728,11 +744,6 @@
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
- if (call.isSelfManaged()) {
- Log.i(this, "onExternalCallChanged: skipped self-managed %s", call);
- return;
- }
-
Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
List<ComponentName> componentsUpdated = new ArrayList<>();
@@ -748,6 +759,10 @@
continue;
}
+ if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+ continue;
+ }
+
componentsUpdated.add(info.getComponentName());
IInCallService inCallService = entry.getValue();
@@ -798,11 +813,6 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
- if (call.isSelfManaged()) {
- Log.i(this, "onExternalCallChanged: skipped self-managed %s", call);
- return;
- }
-
updateCall(call);
}
@@ -811,11 +821,6 @@
Call call,
ConnectionServiceWrapper oldService,
ConnectionServiceWrapper newService) {
- if (call.isSelfManaged()) {
- Log.i(this, "onConnectionServiceChanged: skipped self-managed %s", call);
- return;
- }
-
updateCall(call);
}
@@ -861,11 +866,6 @@
@Override
public void onIsConferencedChanged(Call call) {
- if (call.isSelfManaged()) {
- Log.i(this, "onIsConferencedChanged: skipped self-managed %s", call);
- return;
- }
-
Log.d(this, "onIsConferencedChanged %s", call);
updateCall(call);
}
@@ -909,6 +909,37 @@
}
}
+ private void notifyRttInitiationFailure(Call call, int reason) {
+ if (!mInCallServices.isEmpty()) {
+ mInCallServices.entrySet().stream()
+ .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+ .forEach((entry) -> {
+ try {
+ Log.i(this, "notifyRttFailure, call %s, incall %s",
+ call, entry.getKey());
+ entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call),
+ reason);
+ } catch (RemoteException ignored) {
+ }
+ });
+ }
+ }
+
+ private void notifyRemoteRttRequest(Call call, int requestId) {
+ if (!mInCallServices.isEmpty()) {
+ mInCallServices.entrySet().stream()
+ .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+ .forEach((entry) -> {
+ try {
+ Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
+ call, entry.getKey());
+ entry.getValue().onRttUpgradeRequest(
+ mCallIdMapper.getCallId(call), requestId);
+ } catch (RemoteException ignored) {
+ }
+ });
+ }
+ }
/**
* Unbinds an existing bound connection to the in-call app.
*/
@@ -992,7 +1023,7 @@
// Last Resort: Try to bind to the ComponentName given directly.
Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
+ componentName +". Trying to bind anyway.");
- return new InCallServiceInfo(componentName, false, type);
+ return new InCallServiceInfo(componentName, false, false, type);
}
}
@@ -1041,12 +1072,15 @@
boolean isExternalCallsSupported = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
+ boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
+ serviceInfo.metaData.getBoolean(
+ TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
if (requestedType == 0 || requestedType == getInCallServiceType(entry.serviceInfo,
packageManager)) {
retval.add(new InCallServiceInfo(
new ComponentName(serviceInfo.packageName, serviceInfo.name),
- isExternalCallsSupported, requestedType));
+ isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
}
}
}
@@ -1160,7 +1194,7 @@
int numCallsSent = 0;
for (Call call : calls) {
try {
- if (call.isSelfManaged() ||
+ if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
(call.isExternalCall() && !info.isExternalCallsSupported())) {
continue;
}
@@ -1229,6 +1263,10 @@
continue;
}
+ if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+ continue;
+ }
+
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
call,
videoProviderChanged /* includeVideoProvider */,
diff --git a/src/com/android/server/telecom/InterruptionFilterProxy.java b/src/com/android/server/telecom/InterruptionFilterProxy.java
index 434c341..b659de8 100644
--- a/src/com/android/server/telecom/InterruptionFilterProxy.java
+++ b/src/com/android/server/telecom/InterruptionFilterProxy.java
@@ -24,4 +24,5 @@
public interface InterruptionFilterProxy {
void setInterruptionFilter(int interruptionFilter);
int getCurrentInterruptionFilter();
+ String getInterruptionModeInitiator();
}
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index e855549..cbe3676 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -332,7 +332,10 @@
android.telecom.Call.Details.PROPERTY_IS_EXTERNAL_CALL,
Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY,
- android.telecom.Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY
+ android.telecom.Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY,
+
+ Connection.PROPERTY_SELF_MANAGED,
+ android.telecom.Call.Details.PROPERTY_SELF_MANAGED
};
private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 51f11d7..74eb8bc 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -648,6 +648,26 @@
}
/**
+ * @see android.telecom.TelecomManager#isInManagedCall
+ */
+ @Override
+ public boolean isInManagedCall(String callingPackage) {
+ try {
+ Log.startSession("TSI.iIMC");
+ if (!canReadPhoneState(callingPackage, "isInManagedCall")) {
+ throw new SecurityException("Only the default dialer or caller with " +
+ "READ_PHONE_STATE permission can use this method.");
+ }
+
+ synchronized (mLock) {
+ return mCallsManager.hasOngoingManagedCalls();
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* @see android.telecom.TelecomManager#isRinging
*/
@Override
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index 50b8901..a76821a 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -94,8 +94,8 @@
int subId2 = telephonyManager.getSubIdForPhoneAccount(account2);
if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- retval = (SubscriptionManager.getSlotId(subId1) <
- SubscriptionManager.getSlotId(subId2)) ? -1 : 1;
+ retval = (SubscriptionManager.getSlotIndex(subId1) <
+ SubscriptionManager.getSlotIndex(subId2)) ? -1 : 1;
}
// Then order by package
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 480d8c9..62613b0 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -303,10 +304,12 @@
* @param callingPackage The package calling in.
* @param callingUid The UID of the caller.
* @param callingPid The PID of the caller.
+ * @param targetSdkVersion The target SDK version of the calling InCallService where the camera
+ * request originated.
*/
@Override
public void onSetCamera(String cameraId, String callingPackage, int callingUid,
- int callingPid) {
+ int callingPid, int targetSdkVersion) {
synchronized (mLock) {
logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage +
"; callingUid=" + callingUid);
@@ -315,16 +318,25 @@
if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) {
// Calling app is not permitted to use the camera. Ignore the request and send
// back a call session event indicating the error.
- Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, "
- + "pid=%d",
- callingPackage, callingUid, callingPid);
- VideoProviderProxy.this.handleCallSessionEvent(
- Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
+ Log.i(this, "onSetCamera: camera permission denied; package=%s, uid=%d, "
+ + "pid=%d, targetSdkVersion=%d",
+ callingPackage, callingUid, callingPid, targetSdkVersion);
+
+ // API 26 introduces a new camera permission error we can use here since the
+ // caller supports that API version.
+ if (targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+ VideoProviderProxy.this.handleCallSessionEvent(
+ Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
+ } else {
+ VideoProviderProxy.this.handleCallSessionEvent(
+ Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE);
+ }
return;
}
}
try {
- mConectionServiceVideoProvider.setCamera(cameraId, callingPackage);
+ mConectionServiceVideoProvider.setCamera(cameraId, callingPackage,
+ targetSdkVersion);
} catch (RemoteException e) {
VideoProviderProxy.this.handleCallSessionEvent(
Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE);
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index ea1db36..a066b6c 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -26,6 +26,7 @@
import android.os.IBinder;
import android.os.PowerManager;
import android.os.ServiceManager;
+import android.service.notification.ZenModeConfig;
import android.telecom.Log;
import com.android.internal.telephony.CallerInfoAsyncQuery;
@@ -170,6 +171,15 @@
public int getCurrentInterruptionFilter() {
return notificationManager.getCurrentInterruptionFilter();
}
+
+ @Override
+ public String getInterruptionModeInitiator() {
+ ZenModeConfig config = notificationManager.getZenModeConfig();
+ if (config.manualRule != null) {
+ return config.manualRule.enabler;
+ }
+ return null;
+ }
}
));
}
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index a6a0d76..cc6470e 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -120,6 +120,10 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.telecom.testapps.ACTION_REMOTE_RTT_UPGRADE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<receiver android:name="com.android.server.telecom.testapps.CallNotificationReceiver"
@@ -173,6 +177,14 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.server.telecom.testapps.IncomingSelfManagedCallActivity"
+ android:label="@string/selfManagedCallingActivityLabel"
+ android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
<service android:name="com.android.server.telecom.testapps.SelfManagedConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
@@ -180,5 +192,9 @@
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
+
+ <receiver android:exported="false"
+ android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+ android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver" />
</application>
</manifest>
diff --git a/testapps/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/res/drawable-hdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..ed3ee45
--- /dev/null
+++ b/testapps/res/drawable-hdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/res/drawable-mdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..a4add51
--- /dev/null
+++ b/testapps/res/drawable-mdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/res/drawable-xhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..41558f2
--- /dev/null
+++ b/testapps/res/drawable-xhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/res/drawable-xxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..6006b12
--- /dev/null
+++ b/testapps/res/drawable-xxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/res/drawable-xxxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..4f935bf
--- /dev/null
+++ b/testapps/res/drawable-xxxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index 3ca8781..502bdf4 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -26,7 +26,8 @@
android:divider="#FFCC00"
android:dividerHeight="4px">
</ListView>
- <LinearLayout
+ <GridLayout
+ android:columnCount="4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
@@ -55,5 +56,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/answerCallButton"/>
- </LinearLayout>
+ <Button
+ android:id="@+id/start_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/startRttButton"/>
+ <Button
+ android:id="@+id/accept_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/acceptRttButton"/>
+ </GridLayout>
</LinearLayout>
diff --git a/testapps/res/layout/self_managed_incoming_call.xml b/testapps/res/layout/self_managed_incoming_call.xml
new file mode 100644
index 0000000..c346342
--- /dev/null
+++ b/testapps/res/layout/self_managed_incoming_call.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:text="Incoming Call!"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/incomingCallText" />
+
+ <Button
+ android:text="Answer Call"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/answerCallButton" />
+
+ <Button
+ android:text="Reject Call"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/rejectCallButton" />
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index f239997..144d6ec 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -68,6 +68,11 @@
android:layout_height="wrap_content"
android:text="Incoming Call"
android:id="@+id/placeIncomingCallButton" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Incoming Call (Delay)"
+ android:id="@+id/placeIncomingCallDelayButton" />
</TableRow>
<TableRow android:layout_span="2">
<ListView
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 56cb476..9dc7cae 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -48,6 +48,10 @@
<string name="endRttButton">End RTT</string>
+ <string name="startRttButton">Start RTT</string>
+
+ <string name="acceptRttButton">Accept RTT request</string>
+
<string name="muteButton">Mute</string>
<string name="holdButton">Hold</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 8fd2378..2d43e76 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -144,4 +144,9 @@
intent.setData(data);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
+
+ public static void remoteRttUpgrade(Context context) {
+ final Intent intent = new Intent(TestCallActivity.ACTION_REMOTE_RTT_UPGRADE);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
}
diff --git a/testapps/src/com/android/server/telecom/testapps/IncomingSelfManagedCallActivity.java b/testapps/src/com/android/server/telecom/testapps/IncomingSelfManagedCallActivity.java
new file mode 100644
index 0000000..05b6a6b
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/IncomingSelfManagedCallActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.testapps;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+import android.telephony.DisconnectCause;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.server.telecom.testapps.R;
+
+/**
+ * Sample Incoming Call activity for Self-Managed calls.
+ */
+public class IncomingSelfManagedCallActivity extends Activity {
+ public static final String EXTRA_CALL_ID = "com.android.server.telecom.testapps.extra.CALL_ID";
+
+ private Button mAnswerCallButton;
+ private Button mRejectCallButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent launchingIntent = getIntent();
+ int callId = launchingIntent.getIntExtra(EXTRA_CALL_ID, 0);
+ Log.i(this, "showing fullscreen answer ux for call id %d", callId);
+
+ setContentView(R.layout.self_managed_incoming_call);
+ final SelfManagedConnection connection = SelfManagedCallList.getInstance()
+ .getConnectionById(callId);
+ mAnswerCallButton = (Button) findViewById(R.id.answerCallButton);
+ mAnswerCallButton.setOnClickListener((View v) -> {
+ if (connection != null) {
+ connection.setConnectionActive();
+ }
+ finish();
+ });
+ mRejectCallButton = (Button) findViewById(R.id.rejectCallButton);
+ mRejectCallButton.setOnClickListener((View v) -> {
+ if (connection != null) {
+ connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
+ connection.destroy();
+ }
+ finish();
+ });
+ }
+}
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/RttChatbot.java b/testapps/src/com/android/server/telecom/testapps/RttChatbot.java
index 3b16bd4..44439ee 100644
--- a/testapps/src/com/android/server/telecom/testapps/RttChatbot.java
+++ b/testapps/src/com/android/server/telecom/testapps/RttChatbot.java
@@ -47,6 +47,8 @@
private final Random mRandom = new Random();
private final String[] mOneLiners;
private Handler mHandler;
+ private HandlerThread mSenderThread;
+ private Thread mReceiverThread;
private final class ReplyHandler extends Handler {
private StringBuilder mInputSoFar;
@@ -110,8 +112,9 @@
Log.i(LOG_TAG, "Starting RTT chatbot.");
HandlerThread ht = new HandlerThread("RttChatbotSender");
ht.start();
+ mSenderThread = ht;
mHandler = new ReplyHandler(ht.getLooper());
- Thread receiveThread = new Thread(() -> {
+ mReceiverThread = new Thread(() -> {
while (true) {
String charsReceived = mRttTextStream.read();
if (charsReceived == null) {
@@ -129,6 +132,15 @@
.sendToTarget();
}
}, "RttChatbotReceiver");
- receiveThread.start();
+ mReceiverThread.start();
+ }
+
+ public void stop() {
+ if (mSenderThread != null && mSenderThread.isAlive()) {
+ mSenderThread.quit();
+ }
+ if (mReceiverThread != null && mReceiverThread.isAlive()) {
+ mReceiverThread.interrupt();
+ }
}
}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index e0d7442..1d6367e 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
/**
* Manages the list of {@link SelfManagedConnection} active in the sample third-party calling app.
@@ -64,6 +65,25 @@
private List<SelfManagedConnection> mConnections = new ArrayList<>();
+ private SelfManagedConnection.Listener mConnectionListener =
+ new SelfManagedConnection.Listener() {
+ @Override
+ public void onConnectionStateChanged(SelfManagedConnection connection) {
+ notifyCallModified();
+ }
+
+ @Override
+ public void onConnectionRemoved(SelfManagedConnection connection) {
+ removeConnection(connection);
+ notifyCallModified();
+ }
+ };
+
+ public SelfManagedConnection.Listener getConnectionListener() {
+ return mConnectionListener;
+ }
+
+
public void setListener(Listener listener) {
mListener = listener;
}
@@ -125,6 +145,13 @@
return mConnections;
}
+ public SelfManagedConnection getConnectionById(int callId) {
+ Optional<SelfManagedConnection> foundOptional = mConnections.stream()
+ .filter((c) -> {return c.getCallId() == callId;})
+ .findFirst();
+ return foundOptional.orElse(null);
+ }
+
public void notifyCallModified() {
if (mListener != null) {
mListener.onConnectionListChanged();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallNotificationReceiver.java
new file mode 100644
index 0000000..81a9bfa
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallNotificationReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.testapps;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telecom.Log;
+import android.telephony.DisconnectCause;
+
+/**
+ * Handles actions from the self-managed calling sample app incoming call UX.
+ */
+public class SelfManagedCallNotificationReceiver extends BroadcastReceiver {
+ public static final String ACTION_ANSWER_CALL =
+ "com.android.server.telecom.testapps.action.ANSWER_CALL";
+ public static final String ACTION_REJECT_CALL =
+ "com.android.server.telecom.testapps.action.REJECT_CALL";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ int callId = intent.getIntExtra(IncomingSelfManagedCallActivity.EXTRA_CALL_ID, 0);
+ NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ SelfManagedConnection connection = SelfManagedCallList.getInstance()
+ .getConnectionById(callId);
+ switch (action) {
+ case ACTION_ANSWER_CALL:
+ Log.i(this, "onReceive - answerCall %d", callId);
+ if (connection != null) {
+ connection.setConnectionActive();
+ }
+ notificationManager.cancel(SelfManagedConnection.CALL_NOTIFICATION, callId);
+ break;
+
+ case ACTION_REJECT_CALL:
+ Log.i(this, "onReceive - rejectCall %d", callId);
+ if (connection != null) {
+ connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
+ connection.destroy();
+ }
+ notificationManager.cancel(SelfManagedConnection.CALL_NOTIFICATION, callId);
+ break;
+ }
+ }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 8b7eae0..c8f6157 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -24,6 +24,7 @@
import android.telecom.TelecomManager;
import android.util.Log;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
@@ -45,6 +46,7 @@
private CheckBox mCheckIfPermittedBeforeCalling;
private Button mPlaceOutgoingCallButton;
private Button mPlaceIncomingCallButton;
+ private Button mPlaceIncomingCallDelayButton;
private RadioButton mUseAcct1Button;
private RadioButton mUseAcct2Button;
private EditText mNumber;
@@ -76,6 +78,12 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ int flags =
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+
+ getWindow().addFlags(flags);
setContentView(R.layout.self_managed_sample_main);
mCheckIfPermittedBeforeCalling = (CheckBox) findViewById(
R.id.checkIfPermittedBeforeCalling);
@@ -93,6 +101,20 @@
placeIncomingCall();
}
});
+
+ mPlaceIncomingCallDelayButton = (Button) findViewById(R.id.placeIncomingCallDelayButton);
+ mPlaceIncomingCallDelayButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Delay the incoming call so that we can turn off the screen and
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ placeIncomingCall();
+ }
+ });
mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
mNumber = (EditText) findViewById(R.id.phoneNumber);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 5fd8eba..051948b 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -16,31 +16,55 @@
package com.android.server.telecom.testapps;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.media.MediaPlayer;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
+import com.android.server.telecom.testapps.R;
+
/**
* Sample self-managed {@link Connection} for a self-managed {@link ConnectionService}.
* <p>
* See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
*/
public class SelfManagedConnection extends Connection {
+ public static class Listener {
+ public void onConnectionStateChanged(SelfManagedConnection connection) {}
+ public void onConnectionRemoved(SelfManagedConnection connection) {}
+ }
+
public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
"com.android.server.telecom.testapps.extra.PHONE_ACCOUNT_HANDLE";
+ public static final String CALL_NOTIFICATION = "com.android.server.telecom.testapps.CALL";
+ private static int sNextCallId = 1;
+
+ private final int mCallId;
+ private final Context mContext;
private final SelfManagedCallList mCallList;
private final MediaPlayer mMediaPlayer;
private final boolean mIsIncomingCall;
private boolean mIsIncomingCallUiShowing;
+ private Listener mListener;
SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
mCallList = callList;
mMediaPlayer = createMediaPlayer(context);
mIsIncomingCall = isIncoming;
+ mContext = context;
+ mCallId = sNextCallId++;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
}
/**
@@ -52,20 +76,82 @@
mCallList.notifyCallModified();
}
+ @Override
+ public void onShowIncomingCallUi() {
+ // Create the fullscreen intent used to show the fullscreen incoming call UX.
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(mContext, IncomingSelfManagedCallActivity.class);
+ intent.putExtra(IncomingSelfManagedCallActivity.EXTRA_CALL_ID, mCallId);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, intent, 0);
+
+ // Build the notification as an ongoing high priority item.
+ final Notification.Builder builder = new Notification.Builder(mContext);
+ builder.setOngoing(true);
+ builder.setPriority(Notification.PRIORITY_HIGH);
+
+ // Set up the main intent to send the user to the incoming call screen.
+ builder.setContentIntent(pendingIntent);
+ builder.setFullScreenIntent(pendingIntent, true);
+
+ // Setup notification content.
+ builder.setSmallIcon(R.drawable.ic_android_black_24dp);
+ builder.setContentTitle("Incoming call...");
+ builder.setContentText("Incoming test call from " + getAddress());
+
+ // Setup answer and reject call button
+ final Intent answerIntent = new Intent(
+ SelfManagedCallNotificationReceiver.ACTION_ANSWER_CALL, null, mContext,
+ SelfManagedCallNotificationReceiver.class);
+ answerIntent.putExtra(IncomingSelfManagedCallActivity.EXTRA_CALL_ID, mCallId);
+ final Intent rejectIntent = new Intent(
+ SelfManagedCallNotificationReceiver.ACTION_REJECT_CALL, null, mContext,
+ SelfManagedCallNotificationReceiver.class);
+ rejectIntent.putExtra(IncomingSelfManagedCallActivity.EXTRA_CALL_ID, mCallId);
+
+ builder.addAction(
+ new Notification.Action.Builder(
+ Icon.createWithResource(mContext, R.drawable.ic_android_black_24dp),
+ "Answer",
+ PendingIntent.getBroadcast(mContext, 0, answerIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build());
+ builder.addAction(
+ new Notification.Action.Builder(
+ Icon.createWithResource(mContext, R.drawable.ic_android_black_24dp),
+ "Reject",
+ PendingIntent.getBroadcast(mContext, 0, rejectIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build());
+
+ NotificationManager notificationManager = mContext.getSystemService(
+ NotificationManager.class);
+ notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
+ }
+
public void setConnectionActive() {
mMediaPlayer.start();
setActive();
+ if (mListener != null ) {
+ mListener.onConnectionStateChanged(this);
+ }
}
public void setConnectionHeld() {
mMediaPlayer.pause();
setOnHold();
+ if (mListener != null ) {
+ mListener.onConnectionStateChanged(this);
+ }
}
public void setConnectionDisconnected(int cause) {
mMediaPlayer.stop();
setDisconnected(new DisconnectCause(cause));
destroy();
+ if (mListener != null ) {
+ mListener.onConnectionRemoved(this);
+ }
}
public void setIsIncomingCallUiShowing(boolean showing) {
@@ -80,6 +166,10 @@
return mIsIncomingCall;
}
+ public int getCallId() {
+ return mCallId;
+ }
+
private MediaPlayer createMediaPlayer(Context context) {
int audioToPlay = (Math.random() > 0.5f) ? R.raw.sample_audio : R.raw.sample_audio2;
MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 31bbcd6..e1a01ba 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -49,12 +49,14 @@
}
@Override
- public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
mCallList.notifyCreateIncomingConnectionFailed(request);
}
@Override
- public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
mCallList.notifyCreateOutgoingConnectionFailed(request);
}
@@ -62,6 +64,7 @@
private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
SelfManagedConnection connection = new SelfManagedConnection(mCallList,
getApplicationContext(), isIncoming);
+ connection.setListener(mCallList.getConnectionListener());
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
connection.setExtras(request.getExtras());
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
index 76f2058..ae606c8 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
@@ -55,6 +55,9 @@
static final String ACTION_RTT_CALL =
"android.telecom.testapps.ACTION_RTT_CALL";
+ public static final String ACTION_REMOTE_RTT_UPGRADE =
+ "android.telecom.testapps.ACTION_REMOTE_RTT_UPGRADE";
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -73,6 +76,8 @@
this, data, VideoProfile.STATE_AUDIO_ONLY);
} else if (ACTION_SEND_UPGRADE_REQUEST.equals(action)) {
CallNotificationReceiver.sendUpgradeRequest(this, data);
+ } else if (ACTION_REMOTE_RTT_UPGRADE.equals(action)) {
+ CallNotificationReceiver.remoteRttUpgrade(this);
} else {
CallServiceNotifier.getInstance().updateNotification(this);
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index 4419b17..1b32690 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.testapps;
+import android.content.Context;
import android.telecom.Call;
import android.telecom.InCallService;
import android.telecom.VideoProfile;
@@ -23,6 +24,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.widget.Toast;
import java.util.LinkedList;
import java.util.List;
@@ -37,6 +39,10 @@
public static abstract class Listener {
public void onCallAdded(Call call) {}
public void onCallRemoved(Call call) {}
+ public void onRttStarted(Call call) {}
+ public void onRttStopped(Call call) {}
+ public void onRttInitiationFailed(Call call, int reason) {}
+ public void onRttRequest(Call call, int id) {}
}
private static final TestCallList INSTANCE = new TestCallList();
@@ -97,6 +103,8 @@
private Map<Call, TestVideoCallListener> mVideoCallListeners =
new ArrayMap<Call, TestVideoCallListener>();
private Set<Listener> mListeners = new ArraySet<Listener>();
+ private Context mContext;
+ private int mLastRttRequestId = -1;
/**
* Singleton accessor.
@@ -164,6 +172,10 @@
return mCalls.size();
}
+ public int getLastRttRequestId() {
+ return mLastRttRequestId;
+ }
+
/**
* For any video calls tracked, sends an upgrade to video request.
*/
@@ -218,11 +230,29 @@
@Override
public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
Log.v(TAG, "onRttStatusChanged: call = " + call + " " + System.identityHashCode(this));
+ if (enabled) {
+ for (Listener l : mListeners) {
+ l.onRttStarted(call);
+ }
+ } else {
+ for (Listener l : mListeners) {
+ l.onRttStopped(call);
+ }
+ }
+ }
- if (call != null) {
- // Did you have another call? Well too bad, this class isn't gonna handle it.
- mCalls.clear();
- mCalls.add(call);
+ @Override
+ public void onRttInitiationFailure(Call call, int reason) {
+ for (Listener l : mListeners) {
+ l.onRttInitiationFailed(call, reason);
+ }
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ mLastRttRequestId = id;
+ for (Listener l : mListeners) {
+ l.onRttRequest(call, id);
}
}
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
index c2d8852..abb9108 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
@@ -124,6 +124,26 @@
}
setConferenceableConnections(c);
}
+
+ @Override
+ public void onRttInitiationSuccess(RemoteConnection connection) {
+ sendRttInitiationSuccess();
+ }
+
+ @Override
+ public void onRttInitiationFailure(RemoteConnection connection, int reason) {
+ sendRttInitiationFailure(reason);
+ }
+
+ @Override
+ public void onRttSessionRemotelyTerminated(RemoteConnection connection) {
+ sendRttSessionRemotelyTerminated();
+ }
+
+ @Override
+ public void onRemoteRttRequest(RemoteConnection connection) {
+ sendRemoteRttRequest();
+ }
};
private final RemoteConnection mRemote;
@@ -143,13 +163,17 @@
mRemote.abort();
}
- /** ${inheritDoc} */
+ /**
+ * ${inheritDoc}
+ */
@Override
public void onAnswer(int videoState) {
mRemote.answer(videoState);
}
- /** ${inheritDoc} */
+ /**
+ * ${inheritDoc}
+ */
@Override
public void onDisconnect() {
mRemote.disconnect();
@@ -160,19 +184,25 @@
mRemote.playDtmfTone(c);
}
- /** ${inheritDoc} */
+ /**
+ * ${inheritDoc}
+ */
@Override
public void onHold() {
mRemote.hold();
}
- /** ${inheritDoc} */
+ /**
+ * ${inheritDoc}
+ */
@Override
public void onReject() {
mRemote.reject();
}
- /** ${inheritDoc} */
+ /**
+ * ${inheritDoc}
+ */
@Override
public void onUnhold() {
mRemote.unhold();
@@ -183,6 +213,21 @@
mRemote.setCallAudioState(state);
}
+ @Override
+ public void onStartRtt(RttTextStream rttTextStream) {
+ mRemote.startRtt(rttTextStream);
+ }
+
+ @Override
+ public void onStopRtt() {
+ mRemote.stopRtt();
+ }
+
+ @Override
+ public void handleRttUpgradeResponse(RttTextStream rttTextStream) {
+ mRemote.sendRttUpgradeResponse(rttTextStream);
+ }
+
private void setState(int state) {
log("setState: " + state);
switch (state) {
@@ -201,7 +246,6 @@
}
}
}
-
public final class TestManagedConference extends Conference {
private final RemoteConference.Callback mRemoteCallback = new RemoteConference.Callback() {
@Override
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 6c07073..71af9a8 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -25,7 +25,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.ParcelFileDescriptor;
import android.support.v4.content.LocalBroadcastManager;
import android.telecom.Conference;
import android.telecom.Connection;
@@ -39,8 +38,6 @@
import android.telecom.Log;
import android.widget.Toast;
-import com.android.server.telecom.testapps.R;
-
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
@@ -135,6 +132,8 @@
/** Used to cleanup camera and media when done with connection. */
private TestVideoProvider mTestVideoCallProvider;
+ private ConnectionRequest mOriginalRequest;
+ private RttChatbot mRttChatbot;
private BroadcastReceiver mHangupReceiver = new BroadcastReceiver() {
@Override
@@ -154,8 +153,16 @@
}
};
- TestConnection(boolean isIncoming) {
+ private BroadcastReceiver mRttUpgradeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sendRemoteRttRequest();
+ }
+ };
+
+ TestConnection(boolean isIncoming, ConnectionRequest request) {
mIsIncoming = isIncoming;
+ mOriginalRequest = request;
// Assume all calls are video capable.
int capabilities = getConnectionCapabilities();
capabilities |= CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
@@ -167,6 +174,12 @@
capabilities |= CAPABILITY_RESPOND_VIA_TEXT;
setConnectionCapabilities(capabilities);
+ int properties = getConnectionProperties();
+ if (mOriginalRequest.isRequestingRtt()) {
+ properties |= PROPERTY_IS_RTT;
+ }
+ setConnectionProperties(properties);
+
if (isIncoming) {
putExtra(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
}
@@ -177,17 +190,24 @@
filter.addDataScheme("int");
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
mUpgradeRequestReceiver, filter);
+
+ LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
+ mRttUpgradeReceiver,
+ new IntentFilter(TestCallActivity.ACTION_REMOTE_RTT_UPGRADE));
}
void startOutgoing() {
setDialing();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- setActive();
- activateCall(TestConnection.this);
- }
+ mHandler.postDelayed(() -> {
+ setActive();
+ activateCall(TestConnection.this);
}, 4000);
+ if (mOriginalRequest.isRequestingRtt()) {
+ Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
+ mRttChatbot = new RttChatbot(getApplicationContext(),
+ mOriginalRequest.getRttTextStream());
+ mRttChatbot.start();
+ }
}
/** ${inheritDoc} */
@@ -204,6 +224,12 @@
activateCall(this);
setActive();
updateConferenceable();
+ if (mOriginalRequest.isRequestingRtt()) {
+ Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
+ mRttChatbot = new RttChatbot(getApplicationContext(),
+ mOriginalRequest.getRttTextStream());
+ mRttChatbot.start();
+ }
}
/** ${inheritDoc} */
@@ -246,6 +272,39 @@
setActive();
}
+ @Override
+ public void onStopRtt() {
+ int newProperties = getConnectionProperties() & ~PROPERTY_IS_RTT;
+ setConnectionProperties(newProperties);
+ mRttChatbot.stop();
+ mRttChatbot = null;
+ }
+
+ @Override
+ public void handleRttUpgradeResponse(RttTextStream rttTextStream) {
+ Log.i(this, "RTT request response was %s", rttTextStream == null);
+ if (rttTextStream != null) {
+ mRttChatbot = new RttChatbot(getApplicationContext(), rttTextStream);
+ mRttChatbot.start();
+ setConnectionProperties(getConnectionProperties() | PROPERTY_IS_RTT);
+ sendRttInitiationSuccess();
+ }
+ }
+
+ @Override
+ public void onStartRtt(RttTextStream textStream) {
+ boolean doAccept = Math.random() < 0.5;
+ if (doAccept) {
+ Log.i(this, "Accepting RTT request.");
+ mRttChatbot = new RttChatbot(getApplicationContext(), textStream);
+ mRttChatbot.start();
+ setConnectionProperties(getConnectionProperties() | PROPERTY_IS_RTT);
+ sendRttInitiationSuccess();
+ } else {
+ sendRttInitiationFailure(RttModifyStatus.SESSION_MODIFY_REQUEST_FAIL);
+ }
+ }
+
public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) {
mTestVideoCallProvider = testVideoCallProvider;
}
@@ -273,8 +332,6 @@
/** Used to play an audio tone during a call. */
private MediaPlayer mMediaPlayer;
- // Used to provide text reply in an RTT call
- private RttChatbot mRttChatbot;
@Override
public boolean onUnbind(Intent intent) {
@@ -313,22 +370,12 @@
Toast.LENGTH_SHORT).show();
}
- if (originalRequest.isRequestingRtt()) {
- Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
- mRttChatbot = new RttChatbot(getApplicationContext(),
- originalRequest.getRttTextStream());
- mRttChatbot.start();
- }
-
log("gateway package [" + gatewayPackage + "], original handle [" +
originalHandle + "]");
- final TestConnection connection = new TestConnection(false /* isIncoming */);
+ final TestConnection connection =
+ new TestConnection(false /* isIncoming */, originalRequest);
setAddress(connection, handle);
- if (originalRequest.isRequestingRtt()) {
- connection.setConnectionProperties(
- connection.getConnectionProperties() | Connection.PROPERTY_IS_RTT);
- }
// If the number starts with 555, then we handle it ourselves. If not, then we
// use a remote connection service.
@@ -364,7 +411,7 @@
ComponentName componentName = new ComponentName(this, TestConnectionService.class);
if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
- final TestConnection connection = new TestConnection(true);
+ final TestConnection connection = new TestConnection(true, request);
// Get the stashed intent extra that determines if this is a video call or audio call.
Bundle extras = request.getExtras();
int videoState = extras.getInt(EXTRA_START_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
@@ -393,12 +440,6 @@
"This is a test of call subject lines.");
}
- if (request.isRequestingRtt()) {
- Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
- mRttChatbot = new RttChatbot(getApplicationContext(), request.getRttTextStream());
- mRttChatbot.start();
- }
-
connection.putExtras(connectionExtras);
setAddress(connection, address);
@@ -421,7 +462,7 @@
PhoneAccountHandle accountHandle = request.getAccountHandle();
ComponentName componentName = new ComponentName(this, TestConnectionService.class);
if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
- final TestConnection connection = new TestConnection(false);
+ final TestConnection connection = new TestConnection(false, request);
final Bundle extras = request.getExtras();
final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 809036c..2920ca7 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -25,6 +25,7 @@
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ListView;
+import android.widget.Toast;
public class TestInCallUI extends Activity {
@@ -51,6 +52,28 @@
finish();
}
}
+
+ @Override
+ public void onRttStarted(Call call) {
+ Toast.makeText(TestInCallUI.this, "RTT now enabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttStopped(Call call) {
+ Toast.makeText(TestInCallUI.this, "RTT now disabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttInitiationFailed(Call call, int reason) {
+ Toast.makeText(TestInCallUI.this, String.format("RTT failed to init: %d", reason),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ Toast.makeText(TestInCallUI.this, String.format("RTT request: %d", id),
+ Toast.LENGTH_SHORT).show();
+ }
});
View endCallButton = findViewById(R.id.end_call_button);
@@ -58,6 +81,8 @@
View muteButton = findViewById(R.id.mute_button);
View rttIfaceButton = findViewById(R.id.rtt_iface_button);
View answerButton = findViewById(R.id.answer_button);
+ View startRttButton = findViewById(R.id.start_rtt_button);
+ View acceptRttButton = findViewById(R.id.accept_rtt_button);
endCallButton.setOnClickListener(new OnClickListener() {
@Override
@@ -90,6 +115,7 @@
}
}
});
+
rttIfaceButton.setOnClickListener((view) -> {
Call call = mCallList.getCall(0);
if (call.isRttActive()) {
@@ -98,12 +124,27 @@
startActivity(intent);
}
});
+
answerButton.setOnClickListener(view -> {
Call call = mCallList.getCall(0);
if (call.getState() == Call.STATE_RINGING) {
call.answer(VideoProfile.STATE_AUDIO_ONLY);
}
});
+
+ startRttButton.setOnClickListener(view -> {
+ Call call = mCallList.getCall(0);
+ if (!call.isRttActive()) {
+ call.sendRttRequest();
+ }
+ });
+
+ acceptRttButton.setOnClickListener(view -> {
+ Call call = mCallList.getCall(0);
+ if (!call.isRttActive()) {
+ call.respondToRttRequest(mCallList.getLastRttRequestId(), true);
+ }
+ });
}
/** ${inheritDoc} */
diff --git a/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java b/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
index ce962b4..9bb6977 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
@@ -32,6 +32,7 @@
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -146,6 +147,11 @@
finish();
}
}
+
+ @Override
+ public void onRttStopped(Call call) {
+ TestRttActivity.this.finish();
+ }
});
endRttButton.setOnClickListener((view) -> {
diff --git a/tests/Android.mk b/tests/Android.mk
index f4b8256..1e8b1af 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,6 +20,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-ex-camera2 \
android-support-v4 \
+ android-support-test \
guava \
mockito-target \
platform-test-annotations \
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index f41c13f..86e5559 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.tests;
import android.content.Context;
+import android.os.Build;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.InCallService;
@@ -208,7 +209,7 @@
mConnectionServiceFixtureA.mLatestConnectionId);
InCallService.VideoCall videoCall =
mInCallServiceFixtureX.getCall(callIds.mCallId).getVideoCallImpl(
- mInCallServiceComponentNameX.getPackageName());
+ mInCallServiceComponentNameX.getPackageName(), Build.VERSION.SDK_INT);
videoCall.registerCallback(callback);
((VideoCallImpl) videoCall).setVideoState(VideoProfile.STATE_BIDIRECTIONAL);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index b4a0a07..bba7c5a 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -541,6 +541,36 @@
}
@MediumTest
+ public void testListCurrentCallsHeldImsCepConference() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ Call parentCall = createHeldCall();
+ Call childCall1 = createActiveCall();
+ Call childCall2 = createActiveCall();
+ calls.add(parentCall);
+ calls.add(childCall1);
+ calls.add(childCall2);
+ addCallCapability(parentCall, Connection.CAPABILITY_MANAGE_CONFERENCE);
+ when(childCall1.getParentCall()).thenReturn(parentCall);
+ when(childCall2.getParentCall()).thenReturn(parentCall);
+
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getState()).thenReturn(CallState.ON_HOLD);
+ when(childCall1.getState()).thenReturn(CallState.ACTIVE);
+ when(childCall2.getState()).thenReturn(CallState.ACTIVE);
+
+ when(parentCall.isIncoming()).thenReturn(true);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+
+ verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_HELD), eq(0),
+ eq(true), (String) isNull(), eq(-1));
+ verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(0), eq(CALL_STATE_HELD), eq(0),
+ eq(true), (String) isNull(), eq(-1));
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ @MediumTest
public void testQueryPhoneState() throws Exception {
Call ringingCall = createRingingCall();
when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:5550000"));
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 1f444f2..14fdf05 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -164,6 +164,7 @@
when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
when(mockCallsManager.getLock()).thenReturn(mLock);
+ when(mockCallsManager.hasVideoCall()).thenReturn(false);
when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
when(fakeCall.isAlive()).thenReturn(true);
when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 590304c..3bf044c 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -31,9 +31,9 @@
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
-import android.platform.test.annotations.Postsubmit;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
+import android.support.test.filters.FlakyTest;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -424,7 +424,7 @@
}
@MediumTest
- @Postsubmit
+ @FlakyTest
public void testLogCallDirectionOutgoingWithMultiUserCapability() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
.thenReturn(makeFakePhoneAccount(mOtherUserAccountHandle,
@@ -518,7 +518,7 @@
}
@MediumTest
- @Postsubmit
+ @FlakyTest
public void testLogCallDirectionOutgoingFromManagedProfile() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
.thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle, 0));
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 9907ca7..f053a99 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.CallAudioState;
import android.telecom.Conference;
@@ -239,8 +240,9 @@
}
@Override
- public void createConnectionFailed(String callId, ConnectionRequest request,
- boolean isIncoming, Session.Info sessionInfo) throws RemoteException {
+ public void createConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+ String callId, ConnectionRequest request, boolean isIncoming,
+ Session.Info sessionInfo) throws RemoteException {
Log.i(ConnectionServiceFixture.this, "createConnectionFailed --> " + callId);
if (mConnectionById.containsKey(callId)) {
@@ -328,6 +330,23 @@
}
@Override
+ public void startRtt(String callId, ParcelFileDescriptor fromInCall,
+ ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+
+ }
+
+ @Override
+ public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
+
+ }
+
+ @Override
+ public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
+ ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+
+ }
+
+ @Override
public IBinder asBinder() {
return this;
}
diff --git a/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
new file mode 100644
index 0000000..223cb58
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
@@ -0,0 +1,115 @@
+/* * Copyright (C) 2017 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.media.AudioManager;
+import android.media.ToneGenerator;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.DtmfLocalTonePlayer;
+import com.android.server.telecom.R;
+
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DtmfLocalTonePlayerTest extends TelecomTestCase {
+ private static final int TIMEOUT = 2000;
+ @Mock DtmfLocalTonePlayer.ToneGeneratorProxy mToneProxy;
+ @Mock Call mCall;
+
+ DtmfLocalTonePlayer mPlayer;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mPlayer = new DtmfLocalTonePlayer(mToneProxy);
+ when(mCall.getContext()).thenReturn(mContext);
+ }
+
+ @SmallTest
+ public void testSupportedStart() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(true);
+ when(mToneProxy.isPresent()).thenReturn(true);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy).create();
+ }
+
+ @SmallTest
+ public void testUnsupportedStart() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(false);
+ when(mToneProxy.isPresent()).thenReturn(true);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy, never()).create();
+ }
+
+ @SmallTest
+ public void testPlayToneWhenUninitialized() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(false);
+ when(mToneProxy.isPresent()).thenReturn(false);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ mPlayer.playTone(mCall, '9');
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy, never()).startTone(anyInt(), anyInt());
+ }
+
+ @SmallTest
+ public void testPlayToneWhenInitialized() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(true);
+ when(mToneProxy.isPresent()).thenReturn(true);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ mPlayer.playTone(mCall, '9');
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy).startTone(eq(ToneGenerator.TONE_DTMF_9), eq(-1));
+ }
+
+ @SmallTest
+ public void testStopToneWhenUninitialized() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(false);
+ when(mToneProxy.isPresent()).thenReturn(false);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ mPlayer.stopTone(mCall);
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy, never()).stopTone();
+ }
+
+ @SmallTest
+ public void testStopToneWhenInitialized() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(true);
+ when(mToneProxy.isPresent()).thenReturn(true);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ mPlayer.stopTone(mCall);
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy).stopTone();
+ }
+
+ @SmallTest
+ public void testProperTeardown() {
+ when(mContext.getResources().getBoolean(R.bool.allow_local_dtmf_tones)).thenReturn(true);
+ when(mToneProxy.isPresent()).thenReturn(true);
+ mPlayer.onForegroundCallChanged(null, mCall);
+ mPlayer.onForegroundCallChanged(mCall, null);
+ waitForHandlerAction(mPlayer.getHandler(), TIMEOUT);
+ verify(mToneProxy).release();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 00760fe..7635427 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -125,6 +125,10 @@
}
@Override
+ public void onRttInitiationFailure(String callId, int reason) throws RemoteException {
+ }
+
+ @Override
public IBinder asBinder() {
return this;
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 6baaf85..3d2a52e 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -54,8 +54,8 @@
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.compat.ArgumentMatcher;
import org.mockito.internal.matchers.VarargMatcher;
import java.util.ArrayList;
@@ -114,14 +114,14 @@
}
@Override
- public boolean matches(Object string) {
+ public boolean matchesObject(Object string) {
return mStrings.contains(string);
}
}
private static class IntVarArgMatcher extends ArgumentMatcher<int[]> implements VarargMatcher {
@Override
- public boolean matches(Object argument) {
+ public boolean matchesObject(Object argument) {
return true;
}
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index f590e8c..78d0fec 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -181,6 +181,11 @@
public int getCurrentInterruptionFilter() {
return mInterruptionFilter;
}
+
+ @Override
+ public String getInterruptionModeInitiator() {
+ return "com.android.server.telecom";
+ }
}
@Mock HeadsetMediaButton mHeadsetMediaButton;
@Mock ProximitySensorManager mProximitySensorManager;
diff --git a/tests/src/com/android/server/telecom/tests/VideoProfileTest.java b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
new file mode 100644
index 0000000..8e972e6
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.telecom.VideoProfile;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for the {@link android.telecom.VideoProfile} class.
+ */
+public class VideoProfileTest extends AndroidTestCase {
+ @SmallTest
+ public void testToString() {
+ assertEquals("Audio Only", VideoProfile.videoStateToString(VideoProfile.STATE_AUDIO_ONLY));
+ assertEquals("Audio Tx", VideoProfile.videoStateToString(VideoProfile.STATE_TX_ENABLED));
+ assertEquals("Audio Rx", VideoProfile.videoStateToString(VideoProfile.STATE_RX_ENABLED));
+ assertEquals("Audio Tx Rx", VideoProfile.videoStateToString(
+ VideoProfile.STATE_BIDIRECTIONAL));
+
+ assertEquals("Audio Pause", VideoProfile.videoStateToString(VideoProfile.STATE_PAUSED));
+ assertEquals("Audio Tx Pause", VideoProfile.videoStateToString(
+ VideoProfile.STATE_TX_ENABLED | VideoProfile.STATE_PAUSED));
+ assertEquals("Audio Rx Pause", VideoProfile.videoStateToString(
+ VideoProfile.STATE_RX_ENABLED | VideoProfile.STATE_PAUSED));
+ assertEquals("Audio Tx Rx Pause", VideoProfile.videoStateToString(
+ VideoProfile.STATE_BIDIRECTIONAL | VideoProfile.STATE_PAUSED));
+ }
+
+ @SmallTest
+ public void testIsAudioOnly() {
+ assertFalse(VideoProfile.isAudioOnly(VideoProfile.STATE_RX_ENABLED));
+ assertFalse(VideoProfile.isAudioOnly(VideoProfile.STATE_TX_ENABLED));
+ assertFalse(VideoProfile.isAudioOnly(VideoProfile.STATE_BIDIRECTIONAL));
+
+ assertTrue(VideoProfile.isAudioOnly(VideoProfile.STATE_PAUSED));
+ assertTrue(VideoProfile.isAudioOnly(VideoProfile.STATE_AUDIO_ONLY));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index ee4890f..f6734c7 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -106,7 +107,7 @@
mVideoCallCallback = mock(InCallService.VideoCall.Callback.class);
mVideoCall = mInCallServiceFixtureX.getCall(mCallIds.mCallId).getVideoCallImpl(
- mInCallServiceComponentNameX.getPackageName());
+ mInCallServiceComponentNameX.getPackageName(), Build.VERSION.SDK_INT);
mVideoCallImpl = (VideoCallImpl) mVideoCall;
mVideoCall.registerCallback(mVideoCallCallback);