Merge "Support Connection handover between ConnectionServices."
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 2eeb96c..0dd2cca 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -24,11 +24,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.Trace;
import android.provider.ContactsContract.Contacts;
import android.telecom.CallAudioState;
import android.telecom.Conference;
+import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.Connection;
import android.telecom.GatewayInfo;
@@ -126,6 +128,7 @@
void onExternalCallChanged(Call call, boolean isExternalCall);
void onRttInitiationFailure(Call call, int reason);
void onRemoteRttRequest(Call call, int requestId);
+ void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState);
}
public abstract static class ListenerBase implements Listener {
@@ -197,6 +200,8 @@
public void onRttInitiationFailure(Call call, int reason) {}
@Override
public void onRemoteRttRequest(Call call, int requestId) {}
+ @Override
+ public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState) {}
}
private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -439,6 +444,18 @@
private int mPendingRttRequestId = INVALID_RTT_REQUEST_ID;
/**
+ * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle)},
+ * contains the call which this call is being handed over to.
+ */
+ private Call mHandoverToCall = null;
+
+ /**
+ * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle)},
+ * contains the call which this call is being handed over from.
+ */
+ private Call mHandoverFromCall = null;
+
+ /**
* Persists the specified parameters and initializes the new instance.
*
* @param context The context.
@@ -1027,6 +1044,22 @@
setConnectionProperties(getConnectionProperties());
}
+ public Call getHandoverToCall() {
+ return mHandoverToCall;
+ }
+
+ public void setHandoverToCall(Call call) {
+ mHandoverToCall = call;
+ }
+
+ public Call getHandoverFromCall() {
+ return mHandoverFromCall;
+ }
+
+ public void setHandoverFromCall(Call call) {
+ mHandoverFromCall = call;
+ }
+
private void configureIsWorkCall() {
PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
boolean isWorkCall = false;
@@ -1841,7 +1874,32 @@
*/
public void sendCallEvent(String event, Bundle extras) {
if (mConnectionService != null) {
- mConnectionService.sendCallEvent(this, event, extras);
+ if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
+ // Handover requests are targeted at Telecom, not the ConnectionService.
+ if (extras == null) {
+ Log.w(this, "sendCallEvent: %s event received with null extras.",
+ android.telecom.Call.EVENT_REQUEST_HANDOVER);
+ mConnectionService.sendCallEvent(this,
+ android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ return;
+ }
+ Parcelable parcelable = extras.getParcelable(
+ android.telecom.Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE);
+ if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
+ Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
+ android.telecom.Call.EVENT_REQUEST_HANDOVER);
+ mConnectionService.sendCallEvent(this,
+ android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ return;
+ }
+ PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
+ int videoState = extras.getInt(android.telecom.Call.EXTRA_HANDOVER_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY);
+ requestHandover(phoneAccountHandle, videoState);
+ } else {
+ Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
+ mConnectionService.sendCallEvent(this, event, extras);
+ }
} else {
Log.e(this, new NullPointerException(),
"sendCallEvent failed due to null CS callId=%s", getId());
@@ -2520,4 +2578,15 @@
return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL |
Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
}
+
+ /**
+ * Initiates a handover of this {@link Call} to another {@link PhoneAccount}.
+ * @param handoverToHandle The {@link PhoneAccountHandle} to handover to.
+ * @param videoState The video state of the call when handed over.
+ */
+ private void requestHandover(PhoneAccountHandle handoverToHandle, int videoState) {
+ for (Listener l : mListeners) {
+ l.onHandoverRequested(this, handoverToHandle, videoState);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 9eb8aea..8c75b63 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -634,6 +634,13 @@
}
private void playToneForDisconnectedCall(Call call) {
+ // If this call is being disconnected as a result of being handed over to another call,
+ // we will not play a disconnect tone.
+ if (call.getHandoverToCall() != null || call.getHandoverFromCall() != null) {
+ Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
+ return;
+ }
+
if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
" and there is another call.");
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 45cd488..d26b098 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -656,6 +656,18 @@
}
}
+ /**
+ * A {@link Call} managed by the {@link CallsManager} has requested a handover to another
+ * {@link PhoneAccount}.
+ * @param call The call.
+ * @param handoverTo The {@link PhoneAccountHandle} to handover the call to.
+ * @param videoState The desired video state of the call after handover.
+ */
+ @Override
+ public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState) {
+ requestHandover(call, handoverTo, videoState);
+ }
+
@VisibleForTesting
public Call getForegroundCall() {
if (mCallAudioManager == null) {
@@ -741,6 +753,7 @@
*/
void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
Log.d(this, "processIncomingCallIntent");
+ boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
if (handle == null) {
// Required for backwards compatibility
@@ -818,7 +831,37 @@
// TODO: Move this to be a part of addCall()
call.addListener(this);
- if (call.isSelfManaged() && !isIncomingCallPermitted(call, call.getTargetPhoneAccount())) {
+ boolean isHandoverAllowed = true;
+ if (isHandover) {
+ if (!isHandoverInProgress() &&
+ isHandoverToPhoneAccountSupported(phoneAccountHandle)) {
+ Log.w(this, "processIncomingCallIntent: To account doesn't support handover.");
+ final String handleScheme = handle.getSchemeSpecificPart();
+ Call fromCall = mCalls.stream()
+ .filter((c) -> mPhoneNumberUtilsAdapter.isSamePhoneNumber(
+ c.getHandle().getSchemeSpecificPart(), handleScheme))
+ .findFirst()
+ .orElse(null);
+ if (fromCall != null) {
+ if (!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount())) {
+ Log.w(this, "processIncomingCallIntent: From account doesn't support " +
+ "handover.");
+ isHandoverAllowed = false;
+ }
+ } else {
+ Log.w(this, "processIncomingCallIntent: handover fail; can't find from call.");
+ isHandoverAllowed = false;
+ }
+
+ if (isHandoverAllowed) {
+ // Link the calls so we know we're handing over.
+ fromCall.setHandoverToCall(call);
+ call.setHandoverFromCall(fromCall);
+ }
+ }
+ }
+ if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
+ call.getTargetPhoneAccount()))) {
notifyCreateConnectionFailed(phoneAccountHandle, call);
} else {
call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1621,6 +1664,9 @@
* Removes an existing disconnected call, and notifies the in-call app.
*/
void markCallAsRemoved(Call call) {
+ call.setHandoverToCall(null);
+ call.setHandoverFromCall(null);
+
removeCall(call);
Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
if (mLocallyDisconnectingCalls.contains(call)) {
@@ -2047,6 +2093,31 @@
maybeShowErrorDialogOnDisconnect(call);
Trace.beginSection("onCallStateChanged");
+
+ // If this call became active because it is being handed over from another Call, the
+ // call which was being handed over from can be disconnected at this point.
+ if (call.getHandoverFromCall() != null) {
+ if (newState == CallState.ACTIVE) {
+ Call handoverFrom = call.getHandoverFromCall();
+ Log.addEvent(call, LogUtils.Events.HANDOVER_COMPLETE, "from=%s, to=%s",
+ call.getId(), handoverFrom.getId());
+ Log.addEvent(handoverFrom, LogUtils.Events.HANDOVER_COMPLETE, "from=%s, to=%s",
+ call.getId(), handoverFrom.getId());
+ markCallAsDisconnected(handoverFrom,
+ new DisconnectCause(DisconnectCause.LOCAL));
+ markCallAsRemoved(handoverFrom);
+ call.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_COMPLETE, null);
+ } else if (newState == CallState.DISCONNECTED) {
+ Call handoverFrom = call.getHandoverFromCall();
+ Log.i(this, "Call %s failed to handover from %s.",
+ call.getId(), handoverFrom.getId());
+ Log.addEvent(handoverFrom, LogUtils.Events.HANDOVER_FAILED, "from=%s, to=%s",
+ call.getId(), handoverFrom.getId());
+ handoverFrom.sendCallEvent(
+ android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ }
+ }
+
// Only broadcast state change for calls that are being tracked.
if (mCalls.contains(call)) {
updateCanAddCall();
@@ -2232,7 +2303,8 @@
*/
public boolean shouldShowSystemIncomingCallUi(Call incomingCall) {
return incomingCall.isIncoming() && incomingCall.isSelfManaged() &&
- hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount());
+ hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount()) &&
+ incomingCall.getHandoverFromCall() == null;
}
private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
@@ -2538,10 +2610,11 @@
} else {
// Only permit outgoing calls if there is no ongoing emergency calls and all other calls
// are associated with the current PhoneAccountHandle.
- return !hasEmergencyCall() &&
- !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
- !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
- !hasManagedCalls();
+ return !hasEmergencyCall() && (
+ excludeCall.getHandoverFromCall() != null ||
+ (!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
+ !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
+ !hasManagedCalls()));
}
}
@@ -2672,4 +2745,94 @@
service.createConnectionFailed(call);
}
}
+
+ /**
+ * Called in response to a {@link Call} receiving a {@link Call#sendCallEvent(String, Bundle)}
+ * of type {@link android.telecom.Call#EVENT_REQUEST_HANDOVER} indicating the
+ * {@link android.telecom.InCallService} has requested a handover to another
+ * {@link android.telecom.ConnectionService}.
+ *
+ * We will explicitly disallow a handover when there is an emergency call present.
+ *
+ * @param handoverFromCall The {@link Call} to be handed over.
+ * @param handoverToHandle The {@link PhoneAccountHandle} to hand over the call to.
+ * @param videoState The desired video state of {@link Call} after handover.
+ */
+ private void requestHandover(Call handoverFromCall, PhoneAccountHandle handoverToHandle,
+ int videoState) {
+
+ boolean isHandoverFromSupported = isHandoverFromPhoneAccountSupported(
+ handoverFromCall.getTargetPhoneAccount());
+ boolean isHandoverToSupported = isHandoverToPhoneAccountSupported(handoverToHandle);
+
+ if (!isHandoverFromSupported || !isHandoverToSupported || hasEmergencyCall()) {
+ handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ return;
+ }
+
+ Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, handoverToHandle);
+
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
+ extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+ Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
+ extras, getCurrentUserHandle());
+ Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
+ "handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), handoverToCall.getId());
+ handoverFromCall.setHandoverToCall(handoverToCall);
+ handoverToCall.setHandoverFromCall(handoverFromCall);
+ handoverToCall.setNewOutgoingCallIntentBroadcastIsDone();
+ placeOutgoingCall(handoverToCall, handoverToCall.getHandle(), null /* gatewayInfo */,
+ false /* startwithSpeaker */,
+ videoState);
+ }
+
+ /**
+ * Determines if handover from the specified {@link PhoneAccountHandle} is supported.
+ *
+ * @param from The {@link PhoneAccountHandle} the handover originates from.
+ * @return {@code true} if handover is currently allowed, {@code false} otherwise.
+ */
+ private boolean isHandoverFromPhoneAccountSupported(PhoneAccountHandle from) {
+ return getBooleanPhoneAccountExtra(from, PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
+ }
+
+ /**
+ * Determines if handover to the specified {@link PhoneAccountHandle} is supported.
+ *
+ * @param to The {@link PhoneAccountHandle} the handover it to.
+ * @return {@code true} if handover is currently allowed, {@code false} otherwise.
+ */
+ private boolean isHandoverToPhoneAccountSupported(PhoneAccountHandle to) {
+ return getBooleanPhoneAccountExtra(to, PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
+ }
+
+ /**
+ * Retrieves a boolean phone account extra.
+ * @param handle the {@link PhoneAccountHandle} to retrieve the extra for.
+ * @param key The extras key.
+ * @return {@code true} if the extra {@link PhoneAccount} extra is true, {@code false}
+ * otherwise.
+ */
+ private boolean getBooleanPhoneAccountExtra(PhoneAccountHandle handle, String key) {
+ PhoneAccount phoneAccount = getPhoneAccountRegistrar().getPhoneAccountUnchecked(handle);
+ if (phoneAccount == null) {
+ return false;
+ }
+
+ Bundle fromExtras = phoneAccount.getExtras();
+ if (fromExtras == null) {
+ return false;
+ }
+ return fromExtras.getBoolean(key);
+ }
+
+ /**
+ * Determines if there is an existing handover in process.
+ * @return {@code true} if a call in the process of handover exists, {@code false} otherwise.
+ */
+ private boolean isHandoverInProgress() {
+ return mCalls.stream().filter(c -> c.getHandoverFromCall() != null ||
+ c.getHandoverToCall() != null).count() > 0;
+ }
}
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 121abcf..c61ff65 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -123,6 +123,11 @@
public static final String PROPERTY_CHANGE = "PROPERTY_CHANGE";
public static final String CAPABILITY_CHANGE = "CAPABILITY_CHANGE";
public static final String CONNECTION_EVENT = "CONNECTION_EVENT";
+ public static final String CALL_EVENT = "CALL_EVENT";
+ public static final String HANDOVER_REQUEST = "HANDOVER_REQUEST";
+ public static final String START_HANDOVER = "START_HANDOVER";
+ public static final String HANDOVER_COMPLETE = "HANDOVER_COMPLETE";
+ public static final String HANDOVER_FAILED = "HANDOVER_FAILED";
public static class Timings {
public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
index 8e59a64..aa568a9 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -27,6 +27,7 @@
boolean isLocalEmergencyNumber(Context context, String number);
boolean isPotentialLocalEmergencyNumber(Context context, String number);
boolean isUriNumber(String number);
+ boolean isSamePhoneNumber(String number1, String number2);
String getNumberFromIntent(Intent intent, Context context);
String convertKeypadLettersToDigits(String number);
String stripSeparators(String number);
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
index fa316a5..7ff854e 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -37,6 +37,11 @@
}
@Override
+ public boolean isSamePhoneNumber(String number1, String number2) {
+ return PhoneNumberUtils.compare(number1, number2);
+ }
+
+ @Override
public String getNumberFromIntent(Intent intent, Context context) {
return PhoneNumberUtils.getNumberFromIntent(intent, context);
}
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index a57b158..6980a12 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -196,6 +196,14 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.server.telecom.testapps.HandoverActivity"
+ 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">
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index 502bdf4..36ffb27 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -27,7 +27,7 @@
android:dividerHeight="4px">
</ListView>
<GridLayout
- android:columnCount="4"
+ android:columnCount="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
@@ -66,5 +66,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/acceptRttButton"/>
+ <Button
+ android:id="@+id/request_handover_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/handoverButton"/>
</GridLayout>
</LinearLayout>
diff --git a/testapps/res/layout/self_managed_handover.xml b/testapps/res/layout/self_managed_handover.xml
new file mode 100644
index 0000000..4524370
--- /dev/null
+++ b/testapps/res/layout/self_managed_handover.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="Do you want to handover your call?"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/incomingCallText" />
+
+ <Button
+ android:text="No Way"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/rejectUpgradeButton" />
+
+ <Button
+ android:text="Yes Definitely"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/acceptUpgradeButton" />
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 5a7e500..bfe7550 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -56,6 +56,8 @@
<string name="holdButton">Hold</string>
+ <string name="handoverButton">Handover</string>
+
<string name="inCallUiAppLabel">Test InCall UI</string>
<string name="UssdUiAppLabel">Test Ussd UI</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
new file mode 100644
index 0000000..f33022c
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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.telecom.TelecomManager;
+import android.telephony.DisconnectCause;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Displays a UX to the user confirming whether they want to handover a call to the self-managed CS.
+ */
+public class HandoverActivity extends Activity {
+ public static final String EXTRA_CALL_ID = "com.android.server.telecom.testapps.extra.CALL_ID";
+
+ private Button mAcceptHandoverButton;
+ private Button mRejectHandoverButton;
+
+ @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 upgrade ux for call id %d", callId);
+
+ setContentView(R.layout.self_managed_handover);
+ final SelfManagedConnection connection = SelfManagedCallList.getInstance()
+ .getConnectionById(callId);
+ mAcceptHandoverButton = (Button) findViewById(R.id.acceptUpgradeButton);
+ mAcceptHandoverButton.setOnClickListener((View v) -> {
+ if (connection != null) {
+ connection.setConnectionActive();
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION |
+ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.setClass(this, SelfManagedCallingActivity.class);
+ startActivity(intent);
+ }
+ finish();
+ });
+ mRejectHandoverButton = (Button) findViewById(R.id.rejectUpgradeButton);
+ mRejectHandoverButton.setOnClickListener((View v) -> {
+ if (connection != null) {
+ connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
+ connection.destroy();
+ TelecomManager tm = TelecomManager.from(this);
+ tm.showInCallScreen(false);
+ }
+ finish();
+ });
+ }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 4275079..83efba4 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
+import android.os.Bundle;
import android.telecom.ConnectionRequest;
import android.telecom.Log;
import android.telecom.PhoneAccount;
@@ -100,6 +101,8 @@
public void registerPhoneAccount(Context context, String id, Uri address, String name) {
PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
mPhoneAccounts.put(id, handle);
+ Bundle extras = new Bundle();
+ extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
@@ -107,6 +110,7 @@
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
PhoneAccount.CAPABILITY_VIDEO_CALLING |
PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+ .setExtras(extras)
.setShortDescription(name);
TelecomManager.from(context).registerPhoneAccount(builder.build());
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 766efa5..72a6184 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionService;
@@ -55,6 +57,7 @@
private final boolean mIsIncomingCall;
private boolean mIsIncomingCallUiShowing;
private Listener mListener;
+ private boolean mIsHandover;
SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
mCallList = callList;
@@ -201,6 +204,14 @@
return mCallId;
}
+ public void setIsHandover(boolean isHandover) {
+ mIsHandover = isHandover;
+ }
+
+ public boolean isHandover() {
+ return mIsHandover;
+ }
+
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 1d52a3b..4f28848 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.testapps;
+import android.content.Intent;
import android.os.Bundle;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -79,6 +80,16 @@
if (isIncoming) {
connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
}
+ Bundle requestExtras = request.getExtras();
+ if (requestExtras != null) {
+ connection.setIsHandover(requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER,
+ false));
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(this, HandoverActivity.class);
+ intent.putExtra(HandoverActivity.EXTRA_CALL_ID, connection.getCallId());
+ startActivity(intent);
+ }
// Track the phone account handle which created this connection so we can distinguish them
// in the sample call list later.
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index 1b32690..322c94c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -151,7 +151,9 @@
call.unregisterCallback(this);
for (Listener l : mListeners) {
- l.onCallRemoved(call);
+ if (l != null) {
+ l.onCallRemoved(call);
+ }
}
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 2920ca7..d99798c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -20,6 +20,9 @@
import android.content.Intent;
import android.os.Bundle;
import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Log;
import android.view.View;
@@ -27,6 +30,9 @@
import android.widget.ListView;
import android.widget.Toast;
+import java.util.List;
+import java.util.Optional;
+
public class TestInCallUI extends Activity {
private ListView mListView;
@@ -83,6 +89,7 @@
View answerButton = findViewById(R.id.answer_button);
View startRttButton = findViewById(R.id.start_rtt_button);
View acceptRttButton = findViewById(R.id.accept_rtt_button);
+ View handoverButton = findViewById(R.id.request_handover_button);
endCallButton.setOnClickListener(new OnClickListener() {
@Override
@@ -145,6 +152,15 @@
call.respondToRttRequest(mCallList.getLastRttRequestId(), true);
}
});
+
+ handoverButton.setOnClickListener((v) -> {
+ Call call = mCallList.getCall(0);
+ Bundle extras = new Bundle();
+ extras.putParcelable(Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE,
+ getHandoverToPhoneAccountHandle());
+ extras.putInt(Call.EXTRA_HANDOVER_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL);
+ call.sendCallEvent(Call.EVENT_REQUEST_HANDOVER, extras);
+ });
}
/** ${inheritDoc} */
@@ -167,4 +183,19 @@
protected void onResume() {
super.onResume();
}
+
+ private PhoneAccountHandle getHandoverToPhoneAccountHandle() {
+ TelecomManager tm = TelecomManager.from(this);
+
+ List<PhoneAccountHandle> handles = tm.getAllPhoneAccountHandles();
+ Optional<PhoneAccountHandle> found = handles.stream().filter(h -> {
+ PhoneAccount account = tm.getPhoneAccount(h);
+ Bundle extras = account.getExtras();
+ return extras != null && extras.getBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
+ }).findFirst();
+ PhoneAccountHandle foundHandle = found.orElse(null);
+ Log.i(TestInCallUI.class.getSimpleName(), "getHandoverToPhoneAccountHandle() = " +
+ foundHandle);
+ return foundHandle;
+ }
}