From 79bc1ec8a927149714100b993faa572cefea8965 Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Mon, 22 Jan 2018 15:17:54 -0800 Subject: [PATCH] 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 --- api/current.txt | 2 + api/system-current.txt | 1 + api/test-current.txt | 1 + core/java/android/app/AppOpsManager.java | 21 ++++++- core/res/AndroidManifest.xml | 17 +++++ core/res/res/values/strings.xml | 11 ++++ telecomm/java/android/telecom/Call.java | 9 +++ telecomm/java/android/telecom/Connection.java | 9 +++ .../android/telecom/ConnectionService.java | 62 ++++++++++++++++--- .../java/android/telecom/InCallService.java | 11 ++++ telecomm/java/android/telecom/Phone.java | 7 +++ .../java/android/telecom/TelecomManager.java | 11 ++++ .../internal/telecom/IConnectionService.aidl | 2 + .../internal/telecom/IInCallService.aidl | 2 + 14 files changed, 156 insertions(+), 10 deletions(-) diff --git a/api/current.txt b/api/current.txt index 61465acd362cb..757a66585cbe8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6,6 +6,7 @@ package android { 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"; @@ -40408,6 +40409,7 @@ package android.telecom { 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/api/system-current.txt b/api/system-current.txt index 58d06b3a8ae02..e1ec1deb9def7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -253,6 +253,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setUidMode(java.lang.String, int, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; diff --git a/api/test-current.txt b/api/test-current.txt index c1f1bb6999c59..4e8f904b96b7a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -48,6 +48,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setMode(int, int, java.lang.String, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7ca680239eef0..e923fb217beae 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -263,8 +263,10 @@ public class AppOpsManager { public static final int OP_REQUEST_DELETE_PACKAGES = 72; /** @hide Bind an accessibility service. */ public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; + /** @hide Continue handover of a call from another app */ + public static final int OP_ACCEPT_HANDOVER = 74; /** @hide */ - public static final int _NUM_OP = 74; + public static final int _NUM_OP = 75; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -378,7 +380,13 @@ public class AppOpsManager { /** Answer incoming phone calls */ public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; - + /** + * Accept call handover + * @hide + */ + @SystemApi @TestApi + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; /** @hide */ @SystemApi @TestApi public static final String OPSTR_GPS = "android:gps"; @@ -528,6 +536,7 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -626,6 +635,7 @@ public class AppOpsManager { OP_CHANGE_WIFI_STATE, OP_REQUEST_DELETE_PACKAGES, OP_BIND_ACCESSIBILITY_SERVICE, + OP_ACCEPT_HANDOVER, }; /** @@ -706,6 +716,7 @@ public class AppOpsManager { OPSTR_CHANGE_WIFI_STATE, OPSTR_REQUEST_DELETE_PACKAGES, OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_ACCEPT_HANDOVER, }; /** @@ -787,6 +798,7 @@ public class AppOpsManager { "CHANGE_WIFI_STATE", "REQUEST_DELETE_PACKAGES", "BIND_ACCESSIBILITY_SERVICE", + "ACCEPT_HANDOVER", }; /** @@ -868,6 +880,7 @@ public class AppOpsManager { Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, + Manifest.permission.ACCEPT_HANDOVER, }; /** @@ -950,6 +963,7 @@ public class AppOpsManager { null, // OP_CHANGE_WIFI_STATE null, // REQUEST_DELETE_PACKAGES null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER }; /** @@ -1031,6 +1045,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -1111,6 +1126,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE + AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER }; /** @@ -1195,6 +1211,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ea791a5da5ad0..0861e710a224b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -971,6 +971,23 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 71e963a5bf9e3..d83db87e4832f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1133,6 +1133,17 @@ Allows the app to route its calls through the system in order to improve the calling experience. + + continue a call from another app + + Allows the app to continue a call which was started in another app. + read phone numbers diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index d17bdc85eb22f..6799417939f2f 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -1960,6 +1960,15 @@ public final class Call { } } + /** {@hide} */ + final void internalOnHandoverComplete() { + for (CallbackRecord 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 record : mCallbackRecords) { final Call call = this; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 75224434bc1c5..63f970a43b0f4 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2800,6 +2800,15 @@ public abstract class Connection extends Conferenceable { */ public void onCallEvent(String event, Bundle extras) {} + /** + * Notifies this {@link Connection} that a handover has completed. + *

+ * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, + * Bundle)} on the initiating side of the handover, and + * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}. + */ + public void onHandoverComplete() {} + /** * Notifies this {@link Connection} of a change to the extras made outside the * {@link ConnectionService}. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 6af01aee86b9e..c1040adc5163f 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -140,6 +140,7 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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; @@ -297,6 +299,19 @@ public abstract class ConnectionService extends Service { } } + @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); @@ -1028,6 +1043,19 @@ public abstract class ConnectionService extends Service { } 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 @@ public abstract class ConnectionService extends Service { 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) @@ -1753,6 +1786,19 @@ public abstract class ConnectionService extends Service { } } + /** + * 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. *

diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 74fa62d62ccf1..fcf04c9a7eef8 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -81,6 +81,7 @@ public abstract class InCallService extends Service { 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 @@ public abstract class InCallService extends Service { 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 abstract class InCallService extends Service { 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 b5394b9b02908..99f94f28b6d37 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -230,6 +230,13 @@ public final class Phone { } } + 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 3f5b78a06cc98..1fe5db5d2cc76 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -384,6 +384,17 @@ public class TelecomManager { */ 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} diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 3d04bfc1bae5d..3a84f004462e0 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -107,4 +107,6 @@ oneway interface IConnectionService { void handoverFailed(String callId, in ConnectionRequest request, int error, in Session.Info sessionInfo); + + void handoverComplete(String callId, 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 110109e6e276d..b9563fa7bb185 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -56,4 +56,6 @@ oneway interface IInCallService { void onRttInitiationFailure(String callId, int reason); void onHandoverFailed(String callId, int error); + + void onHandoverComplete(String callId); }