From 858bfaf79c97e000af68649970994ee16bdd08ac 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 Merged-In: If1558f6a23911862c02ac5b18fb62d86911ed7e2 --- api/current.txt | 2 + core/java/android/app/AppOpsManager.java | 21 ++++++- core/res/AndroidManifest.xml | 17 +++++ core/res/res/values/strings.xml | 11 ++++ .../server/pm/PackageManagerService.java | 3 +- 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 + 13 files changed, 156 insertions(+), 11 deletions(-) diff --git a/api/current.txt b/api/current.txt index fba245d386e4d..3e94eeecb01e6 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"; @@ -39332,6 +39333,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/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b331d84010d03..e8fbbd78cffd2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -252,8 +252,10 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { /** 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 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -492,7 +501,8 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { OPSTR_PICTURE_IN_PICTURE, OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, + OPSTR_ACCEPT_HANDOVER }; /** @@ -647,6 +658,7 @@ public class AppOpsManager { "PICTURE_IN_PICTURE", "INSTANT_APP_START_FOREGROUND", "ANSWER_PHONE_CALLS", + "ACCEPT_HANDOVER" }; /** @@ -724,6 +736,7 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE null, // INSTANT_APP_START_FOREGROUND null, // ANSWER_PHONE_CALLS + null, // ACCEPT_HANDOVER }; /** @@ -879,6 +893,7 @@ public class AppOpsManager { false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE false, // INSTANT_APP_START_FOREGROUND false, // ANSWER_PHONE_CALLS + false, // ACCEPT_HANDOVER }; /** @@ -955,6 +970,7 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { 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 fdda55bfa01bd..a8ec420451839 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -950,6 +950,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 befebd9dd00fc..0a36ba7475205 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1076,6 +1076,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/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f182c05cb76df..69afc469bd010 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 8c7d6b30ecdc2..8a4b046516ece 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -1961,6 +1961,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 d71fde28480ce..8150efa725712 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2799,6 +2799,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 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}. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index e37aeb47f1a79..990b0b4eefee8 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 96c6e0a5b7438..7e897453ab59d 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -378,6 +378,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 02e1ff8180665..3dbc8ddc340ff 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -104,6 +104,8 @@ oneway interface IConnectionService { 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 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); }