Add handover permission, fill in some missing API gaps.
Adding the ACCEPT_HANDOVER runtime permission which an app must have in
order to accept handovers (this is per design).
Adding missing onHandoverComplete method in the android.telecom.Connection
API (per design).
Finishing plumbing for android.telecom.Call#onHandoverComplete API.
Fix issue where the new handover API methods would never get called; the
legacy handover extra was being used in this case when it should not have
been.
Bug: 65415068
Test: Verified using new CTS tests
Change-Id: If1558f6a23911862c02ac5b18fb62d86911ed7e2
Merged-In: If1558f6a23911862c02ac5b18fb62d86911ed7e2
diff --git a/api/current.txt b/api/current.txt
index fba245d..3e94eee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6,6 +6,7 @@
public static final class Manifest.permission {
ctor public Manifest.permission();
+ field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
@@ -39332,6 +39333,7 @@
method public void onCallEvent(java.lang.String, android.os.Bundle);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
+ method public void onHandoverComplete();
method public void onHold();
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b331d84..e8fbbd7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -252,8 +252,10 @@
public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
/** @hide Answer incoming phone calls */
public static final int OP_ANSWER_PHONE_CALLS = 69;
+ /** @hide Continue handover of a call from another app */
+ public static final int OP_ACCEPT_HANDOVER = 70;
/** @hide */
- public static final int _NUM_OP = 70;
+ public static final int _NUM_OP = 71;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -365,6 +367,12 @@
/** Answer incoming phone calls */
public static final String OPSTR_ANSWER_PHONE_CALLS
= "android:answer_phone_calls";
+ /***
+ * Accept call handover
+ * @hide
+ */
+ public static final String OPSTR_ACCEPT_HANDOVER
+ = "android:accept_handover";
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
@@ -400,6 +408,7 @@
OP_USE_SIP,
OP_PROCESS_OUTGOING_CALLS,
OP_ANSWER_PHONE_CALLS,
+ OP_ACCEPT_HANDOVER,
// Microphone
OP_RECORD_AUDIO,
// Camera
@@ -492,7 +501,8 @@
OP_REQUEST_INSTALL_PACKAGES,
OP_PICTURE_IN_PICTURE,
OP_INSTANT_APP_START_FOREGROUND,
- OP_ANSWER_PHONE_CALLS
+ OP_ANSWER_PHONE_CALLS,
+ OP_ACCEPT_HANDOVER
};
/**
@@ -570,6 +580,7 @@
OPSTR_PICTURE_IN_PICTURE,
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
+ OPSTR_ACCEPT_HANDOVER
};
/**
@@ -647,6 +658,7 @@
"PICTURE_IN_PICTURE",
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
+ "ACCEPT_HANDOVER"
};
/**
@@ -724,6 +736,7 @@
null, // no permission for entering picture-in-picture on hide
Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
Manifest.permission.ANSWER_PHONE_CALLS,
+ Manifest.permission.ACCEPT_HANDOVER
};
/**
@@ -802,6 +815,7 @@
null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
null, // INSTANT_APP_START_FOREGROUND
null, // ANSWER_PHONE_CALLS
+ null, // ACCEPT_HANDOVER
};
/**
@@ -879,6 +893,7 @@
false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
false, // INSTANT_APP_START_FOREGROUND
false, // ANSWER_PHONE_CALLS
+ false, // ACCEPT_HANDOVER
};
/**
@@ -955,6 +970,7 @@
AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
+ AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
};
/**
@@ -1035,6 +1051,7 @@
false, // OP_PICTURE_IN_PICTURE
false,
false, // ANSWER_PHONE_CALLS
+ false, // ACCEPT_HANDOVER
};
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fdda55b..a8ec420 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -950,6 +950,23 @@
android:description="@string/permdesc_manageOwnCalls"
android:protectionLevel="normal" />
+ <!-- Allows a calling app to continue a call which was started in another app. An example is a
+ video calling app that wants to continue a voice call on the user's mobile network.<p>
+ When the handover of a call from one app to another takes place, there are two devices
+ which are involved in the handover; the initiating and receiving devices. The initiating
+ device is where the request to handover the call was started, and the receiving device is
+ where the handover request is confirmed by the other party.<p>
+ This permission protects access to
+ {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}, which
+ the receiving side of the handover uses to accept a handover.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACCEPT_HANDOVER"
+ android:permissionGroup="android.permission-group.PHONE"
+ android.label="@string/permlab_acceptHandover"
+ android:description="@string/permdesc_acceptHandover"
+ android:protectionLevel="dangerous" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device microphone -->
<!-- ====================================================================== -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index befebd9..0a36ba7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1076,6 +1076,17 @@
<string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
order to improve the calling experience.</string>
+ <!-- Title of an application permission. When granted to a third party app, the user is giving
+ access to the app to continue a call which originated in another app. For example, the
+ user could be in a voice call over their carrier's mobile network, and a third party video
+ calling app wants to continue that voice call as a video call. -->
+ <string name="permlab_acceptHandover">continue a call from another app</string>
+ <!-- Description of an application permission. When granted to a third party app, the user is
+ giving access to the app to continue a call which originated in another app. For example,
+ the user could be in a voice call over their carrier's mobile network, and a third party
+ video calling app wants to continue that voice call as a video call. -->
+ <string name="permdesc_acceptHandover">Allows the app to continue a call which was started in another app.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readPhoneNumbers">read phone numbers</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f182c05cb..69afc46 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -610,7 +610,8 @@
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_NUMBERS,
- Manifest.permission.ANSWER_PHONE_CALLS);
+ Manifest.permission.ANSWER_PHONE_CALLS,
+ Manifest.permission.ACCEPT_HANDOVER);
/**
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 8c7d6b3..8a4b046 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1961,6 +1961,15 @@
}
}
+ /** {@hide} */
+ final void internalOnHandoverComplete() {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Call call = this;
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onHandoverComplete(call));
+ }
+ }
+
private void fireStateChanged(final int newState) {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index d71fde2..8150efa 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2800,6 +2800,15 @@
public void onCallEvent(String event, Bundle extras) {}
/**
+ * Notifies this {@link Connection} that a handover has completed.
+ * <p>
+ * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int,
+ * Bundle)} on the initiating side of the handover, and on the receiving side with
+ * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}.
+ */
+ public void onHandoverComplete() {}
+
+ /**
* Notifies this {@link Connection} of a change to the extras made outside the
* {@link ConnectionService}.
* <p>
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index e37aeb4..990b0b4 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -140,6 +140,7 @@
private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
+ private static final String SESSION_HANDOVER_COMPLETE = "CS.hC";
private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
private static final String SESSION_START_RTT = "CS.+RTT";
private static final String SESSION_STOP_RTT = "CS.-RTT";
@@ -179,6 +180,7 @@
private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30;
private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
private static final int MSG_HANDOVER_FAILED = 32;
+ private static final int MSG_HANDOVER_COMPLETE = 33;
private static Connection sNullConnection;
@@ -298,6 +300,19 @@
}
@Override
+ public void handoverComplete(String callId, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void abort(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_ABORT);
try {
@@ -1028,6 +1043,19 @@
}
break;
}
+ case MSG_HANDOVER_COMPLETE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Log.continueSession((Session) args.arg2,
+ SESSION_HANDLER + SESSION_HANDOVER_COMPLETE);
+ String callId = (String) args.arg1;
+ notifyHandoverComplete(callId);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_ON_EXTRAS_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -1445,19 +1473,24 @@
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
+ boolean isLegacyHandover = request.getExtras() != null &&
+ request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
+ boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
+ TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
- "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
- isIncoming,
- isUnknown);
+ "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
+ callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
+ isHandover);
Connection connection = null;
- if (getApplicationContext().getApplicationInfo().targetSdkVersion >
- Build.VERSION_CODES.O_MR1 && request.getExtras() != null &&
- request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER,false)) {
+ if (isHandover) {
+ PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
+ ? (PhoneAccountHandle) request.getExtras().getParcelable(
+ TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
if (!isIncoming) {
- connection = onCreateOutgoingHandoverConnection(callManagerAccount, request);
+ connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
} else {
- connection = onCreateIncomingHandoverConnection(callManagerAccount, request);
+ connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
}
} else {
connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
@@ -1754,6 +1787,19 @@
}
/**
+ * Notifies a {@link Connection} that a handover has completed.
+ *
+ * @param callId The ID of the call which completed handover.
+ */
+ private void notifyHandoverComplete(String callId) {
+ Log.d(this, "notifyHandoverComplete(%s)", callId);
+ Connection connection = findConnectionForAction(callId, "notifyHandoverComplete");
+ if (connection != null) {
+ connection.onHandoverComplete();
+ }
+ }
+
+ /**
* Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
* <p>
* These extra changes can originate from Telecom itself, or from an {@link InCallService} via
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 74fa62d..fcf04c9 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -81,6 +81,7 @@
private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
private static final int MSG_ON_HANDOVER_FAILED = 12;
+ private static final int MSG_ON_HANDOVER_COMPLETE = 13;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -157,6 +158,11 @@
mPhone.internalOnHandoverFailed(callId, error);
break;
}
+ case MSG_ON_HANDOVER_COMPLETE: {
+ String callId = (String) msg.obj;
+ mPhone.internalOnHandoverComplete(callId);
+ break;
+ }
default:
break;
}
@@ -237,6 +243,11 @@
public void onHandoverFailed(String callId, int error) {
mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget();
}
+
+ @Override
+ public void onHandoverComplete(String callId) {
+ mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
+ }
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index b5394b9..99f94f2 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -230,6 +230,13 @@
}
}
+ final void internalOnHandoverComplete(String callId) {
+ Call call = mCallByTelecomCallId.get(callId);
+ if (call != null) {
+ call.internalOnHandoverComplete();
+ }
+ }
+
/**
* Called to destroy the phone and cleanup any lingering calls.
*/
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 96c6e0a..7e89745 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -379,6 +379,17 @@
public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER";
/**
+ * When {@code true} indicates that a request to create a new connection is for the purpose of
+ * a handover. Note: This is used with the
+ * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} API as part of the
+ * internal communication mechanism with the {@link android.telecom.ConnectionService}. It is
+ * not the same as the legacy {@link #EXTRA_IS_HANDOVER} extra.
+ * @hide
+ */
+ public static final String EXTRA_IS_HANDOVER_CONNECTION =
+ "android.telecom.extra.IS_HANDOVER_CONNECTION";
+
+ /**
* Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source
* {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService}
* the handover is from.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 02e1ff8..3dbc8dd 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,8 @@
void handoverFailed(String callId, in ConnectionRequest request,
int error, in Session.Info sessionInfo);
+ void handoverComplete(String callId, in Session.Info sessionInfo);
+
void connectionServiceFocusLost(in Session.Info sessionInfo);
void connectionServiceFocusGained(in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 110109e..b9563fa 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -56,4 +56,6 @@
void onRttInitiationFailure(String callId, int reason);
void onHandoverFailed(String callId, int error);
+
+ void onHandoverComplete(String callId);
}