Merge "send call events directly to InCallSerivces (for transactional calls)" into udc-dev
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 9226599..75d3416 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -108,7 +108,7 @@
<string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"কল অৱৰোধ"</string>
<string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
<string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
- <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্য়ক্তিগত"</string>
+ <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্যক্তিগত"</string>
<string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
<string name="phone_settings_payphone_txt" msgid="5003987966052543965">"পে\'ফ\'ন"</string>
<string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 7c07654..29fdc4a 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -75,10 +75,10 @@
<string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Тыйым уақытша алынды"</string>
<string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Төтенше жағдай нөмірін терген немесе мәтіндік хабар жіберген соң, төтенше жағдай қызметтері сізге хабарласа алуы үшін тыйым алынады."</string>
<string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Қазір қайта қосу"</string>
- <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бөгелген"</string>
+ <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> блокталған"</string>
<string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> бөгеуден шығарылды"</string>
<string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Жедел қызмет нөмірін бөгеу мүмкін емес."</string>
- <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан бөгелген."</string>
+ <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан блокталған."</string>
<string name="toast_personal_call_msg" msgid="5817631570381795610">"Қоңырау шалу үшін жеке нөмір тергішті пайдалану"</string>
<string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
<string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> бейне қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 4b67e05..25890d9 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -414,6 +414,16 @@
private boolean mIsEmergencyCall;
+ /**
+ * Flag indicating if ECBM is active for the target phone account. This only applies to MT calls
+ * in the scenario of work profiles (when the profile is paused and the user has only registered
+ * a work sim). Normally, MT calls made to the work sim should be rejected when the work apps
+ * are paused. However, when the admin makes a MO ecall, ECBM should be enabled for that sim to
+ * allow non-emergency MT calls. MO calls don't apply because the phone account would be
+ * rejected from selection if the owner is not placing the call.
+ */
+ private boolean mIsInECBM;
+
// The Call is considered an emergency call for testing, but will not actually connect to
// emergency services.
private boolean mIsTestEmergencyCall;
@@ -1592,6 +1602,21 @@
}
/**
+ * @return {@code true} if the target phone account is in ECBM.
+ */
+ public boolean isInECBM() {
+ return mIsInECBM;
+ }
+
+ /**
+ * Set if the target phone account is in ECBM.
+ * @param isInEcbm {@code true} if target phone account is in ECBM, {@code false} otherwise.
+ */
+ public void setIsInECBM(boolean isInECBM) {
+ mIsInECBM = isInECBM;
+ }
+
+ /**
* @return {@code true} if the network has identified this call as an emergency call.
*/
public boolean isNetworkIdentifiedEmergencyCall() {
@@ -1682,6 +1707,11 @@
public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
mTargetPhoneAccountHandle = accountHandle;
+ // Update the last MO emergency call in the helper, if applicable.
+ if (isEmergencyCall() && !isIncoming()) {
+ mCallsManager.getEmergencyCallHelper().setLastOutgoingEmergencyCallPAH(
+ accountHandle);
+ }
for (Listener l : mListeners) {
l.onTargetPhoneAccountChanged(this);
}
@@ -4584,7 +4614,7 @@
throw new UnsupportedOperationException(
"Can't streaming call created by non voip apps");
}
-
+ Log.addEvent(this, LogUtils.Events.START_STREAMING);
synchronized (mLock) {
if (mIsStreaming) {
// ignore
@@ -4604,7 +4634,7 @@
// ignore
return;
}
-
+ Log.addEvent(this, LogUtils.Events.STOP_STREAMING);
mIsStreaming = false;
for (Listener listener : mListeners) {
listener.onCallStreamingStateChanged(this, false /** isStreaming */);
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 8cac314..38e6b00 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -146,6 +146,9 @@
@Override
public void onCallRemoved(Call call) {
+ if (mStreamingCall == call) {
+ mStreamingCall = null;
+ }
if (shouldIgnoreCallForAudio(call)) {
return; // Don't do audio handling for calls in a conference, or external calls.
}
@@ -238,7 +241,7 @@
makeArgsForModeStateMachine());
} else {
Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
- + "s is streaming.", call.getId(), mStreamingCall.getId());
+ + "%s is streaming.", call.getId(), mStreamingCall.getId());
}
} else {
if (mStreamingCall == call) {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 3ced36d..9ad9094 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -665,7 +665,9 @@
@Override
public void enter() {
Log.i(LOG_TAG, "Audio focus entering streaming state");
- mAudioManager.setMode(AudioManager.MODE_CALL_REDIRECT);
+ mLocalLog.log("Enter Streaming");
+ mLocalLog.log("Mode MODE_COMMUNICATION_REDIRECT");
+ mAudioManager.setMode(AudioManager.MODE_COMMUNICATION_REDIRECT);
mMostRecentMode = AudioManager.MODE_NORMAL;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
@@ -685,7 +687,8 @@
MessageArgs args = (MessageArgs) msg.obj;
switch (msg.what) {
case NO_MORE_ACTIVE_OR_DIALING_CALLS:
- // Do nothing.
+ // Switch to either ringing, holding, or inactive
+ transitionTo(calculateProperStateFromArgs(args));
return HANDLED;
case NO_MORE_RINGING_CALLS:
// Do nothing.
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
index 60827e2..82164b3 100644
--- a/src/com/android/server/telecom/CallEndpointController.java
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -25,7 +25,9 @@
import android.telecom.CallEndpoint;
import android.telecom.CallEndpointException;
import android.telecom.Log;
+
import com.android.internal.annotations.VisibleForTesting;
+
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
@@ -96,6 +98,12 @@
return;
}
+ if (isCurrentEndpointRequestedEndpoint(route, bluetoothAddress)) {
+ Log.d(this, "requestCallEndpointChange: requested endpoint is already active");
+ callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+ return;
+ }
+
if (mPendingChangeRequest != null && !mPendingChangeRequest.isDone()) {
mPendingChangeRequest.complete(RESULT_ANOTHER_REQUEST);
mPendingChangeRequest = null;
@@ -116,6 +124,27 @@
mCallsManager.getCallAudioManager().setAudioRoute(route, bluetoothAddress);
}
+ public boolean isCurrentEndpointRequestedEndpoint(int requestedRoute, String requestedAddress) {
+ if (mCallsManager.getCallAudioManager() == null
+ || mCallsManager.getCallAudioManager().getCallAudioState() == null) {
+ return false;
+ }
+ CallAudioState currentAudioState = mCallsManager.getCallAudioManager().getCallAudioState();
+ // requested non-bt endpoint is already active
+ if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH &&
+ requestedRoute == currentAudioState.getRoute()) {
+ return true;
+ }
+ // requested bt endpoint is already active
+ if (requestedRoute == CallAudioState.ROUTE_BLUETOOTH &&
+ currentAudioState.getActiveBluetoothDevice() != null &&
+ requestedAddress.equals(
+ currentAudioState.getActiveBluetoothDevice().getAddress())) {
+ return true;
+ }
+ return false;
+ }
+
private Bundle getErrorResult(int result) {
String message;
int resultCode;
@@ -165,8 +194,7 @@
for (Call call : calls) {
if (call != null && call.getConnectionService() != null) {
call.getConnectionService().onCallEndpointChanged(call, mActiveCallEndpoint);
- }
- else if (call != null && call.getTransactionServiceWrapper() != null) {
+ } else if (call != null && call.getTransactionServiceWrapper() != null) {
call.getTransactionServiceWrapper()
.onCallEndpointChanged(call, mActiveCallEndpoint);
}
@@ -181,8 +209,7 @@
if (call != null && call.getConnectionService() != null) {
call.getConnectionService().onAvailableCallEndpointsChanged(call,
mAvailableCallEndpoints);
- }
- else if (call != null && call.getTransactionServiceWrapper() != null) {
+ } else if (call != null && call.getTransactionServiceWrapper() != null) {
call.getTransactionServiceWrapper()
.onAvailableCallEndpointsChanged(call, mAvailableCallEndpoints);
}
@@ -196,8 +223,7 @@
for (Call call : calls) {
if (call != null && call.getConnectionService() != null) {
call.getConnectionService().onMuteStateChanged(call, isMuted);
- }
- else if (call != null && call.getTransactionServiceWrapper() != null) {
+ } else if (call != null && call.getTransactionServiceWrapper() != null) {
call.getTransactionServiceWrapper().onMuteStateChanged(call, isMuted);
}
}
@@ -207,7 +233,7 @@
Set<CallEndpoint> newAvailableEndpoints = new HashSet<>();
Map<ParcelUuid, String> newBluetoothDevices = new HashMap<>();
- mRouteToTypeMap.forEach((route, type)->{
+ mRouteToTypeMap.forEach((route, type) -> {
if ((state.getSupportedRouteMask() & route) != 0) {
if (type == CallEndpoint.TYPE_STREAMING) {
if (state.getRoute() == CallAudioState.ROUTE_STREAMING) {
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 31b2235..6276a7d 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -36,7 +36,7 @@
import android.telecom.CallException;
import android.telecom.CallStreamingService;
import android.telecom.StreamingCall;
-import android.util.Log;
+import android.telecom.Log;
import com.android.internal.telecom.ICallStreamingService;
import com.android.server.telecom.voip.VoipCallTransaction;
@@ -65,6 +65,9 @@
private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
IBinder service) throws RemoteException {
synchronized (mLock) {
+ Log.i(this, "onConnectedInternal: callid=%s", call.getId());
+ Bundle extras = new Bundle();
+ extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId());
mStreamingCall = call;
mTransactionalServiceWrapper = wrapper;
mService = ICallStreamingService.Stub.asInterface(service);
@@ -74,7 +77,7 @@
mService.onCallStreamingStarted(new StreamingCall(
mTransactionalServiceWrapper.getComponentName(),
mStreamingCall.getCallerDisplayName(),
- mStreamingCall.getContactUri(), new Bundle()));
+ mStreamingCall.getHandle(), extras));
mIsStreaming = true;
}
}
@@ -99,7 +102,6 @@
}
public static class QueryCallStreamingTransaction extends VoipCallTransaction {
- private static final String TAG = QueryCallStreamingTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
public QueryCallStreamingTransaction(CallsManager callsManager) {
@@ -109,7 +111,7 @@
@Override
public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
+ Log.i(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
if (mCallsManager.getCallStreamingController().isStreaming()) {
@@ -126,8 +128,6 @@
}
public static class AudioInterceptionTransaction extends VoipCallTransaction {
- private static final String TAG = AudioInterceptionTransaction.class.getSimpleName();
-
private Call mCall;
private boolean mEnterInterception;
@@ -140,7 +140,7 @@
@Override
public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
+ Log.d(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
if (mEnterInterception) {
@@ -160,7 +160,6 @@
}
public class StreamingServiceTransaction extends VoipCallTransaction {
- private static final String TAG = "StreamingServiceTransaction";
public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
private final TransactionalServiceWrapper mWrapper;
private final Context mContext;
@@ -179,13 +178,13 @@
@SuppressLint("LongLogTag")
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
+ Log.d(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
PackageManager packageManager = mContext.getPackageManager();
if (roleManager == null || packageManager == null) {
- Log.e(TAG, "Can't find system service");
+ Log.w(this, "processTransaction: Can't find system service");
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
return future;
@@ -194,7 +193,7 @@
List<String> holders = roleManager.getRoleHoldersAsUser(
RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
if (holders.isEmpty()) {
- Log.e(TAG, "Can't find streaming app");
+ Log.w(this, "processTransaction: Can't find streaming app");
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
return future;
@@ -205,7 +204,7 @@
List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
PackageManager.GET_META_DATA, mUserHandle);
if (infos.isEmpty()) {
- Log.e(TAG, "Can't find streaming service");
+ Log.w(this, "processTransaction: Can't find streaming service");
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
return future;
@@ -215,7 +214,7 @@
if (serviceInfo.permission == null || !serviceInfo.permission.equals(
Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
- android.telecom.Log.w(TAG, "Must require BIND_CALL_STREAMING_SERVICE: " +
+ Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " +
serviceInfo.packageName);
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
@@ -228,7 +227,7 @@
if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
| Context.BIND_FOREGROUND_SERVICE
| Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
- Log.e(TAG, "Can't bind to streaming service");
+ Log.w(this, "Can't bind to streaming service");
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED,
"STREAMING_FAILED_SENDER_BINDING_ERROR"));
@@ -243,8 +242,6 @@
}
public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
- private static final String TAG = "UnbindStreamingServiceTransaction";
-
public UnbindStreamingServiceTransaction() {
super(mTelecomLock);
}
@@ -252,7 +249,7 @@
@SuppressLint("LongLogTag")
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
+ Log.d(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
resetController();
@@ -285,6 +282,7 @@
StreamingCall.STATE_HOLDING);
case CallState.DISCONNECTING:
case CallState.DISCONNECTED:
+ Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
transaction = new CallStreamingStateChangeTransaction(
StreamingCall.STATE_DISCONNECTED);
default:
@@ -300,8 +298,8 @@
@Override
public void onError(CallException exception) {
- Log.e(String.valueOf(this), "Exception when set call "
- + "streaming state to streaming app: " + exception);
+ Log.e(this, exception, "Exception when set call "
+ + "streaming state to streaming app");
}
});
}
@@ -348,6 +346,7 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
+ Log.i(this, "onServiceConnected: " + name);
onConnectedInternal(mCall, mWrapper, service);
mFuture.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_SUCCEED, null));
@@ -380,7 +379,7 @@
mService.onCallStreamingStopped();
}
} catch (RemoteException e) {
- Log.w(String.valueOf(this), "Exception when stop call streaming:" + e);
+ Log.e(this, e, "Exception when stop call streaming");
}
resetController();
if (!mFuture.isDone()) {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ccc8e59..d457cc8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -24,6 +24,7 @@
import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
import static android.telecom.TelecomManager.ACTION_POST_CALL;
@@ -446,6 +447,7 @@
private final CallStreamingController mCallStreamingController;
private final BlockedNumbersAdapter mBlockedNumbersAdapter;
private final TransactionManager mTransactionManager;
+ private final UserManager mUserManager;
private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -685,6 +687,7 @@
mCallAnomalyWatchdog = callAnomalyWatchdog;
mAsyncTaskExecutor = asyncTaskExecutor;
+ mUserManager = mContext.getSystemService(UserManager.class);
}
public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -1534,7 +1537,22 @@
CallFailureCause startFailCause =
checkIncomingCallPermitted(call, call.getTargetPhoneAccount());
- if (!isHandoverAllowed ||
+ // Check if the target phone account is possibly in ECBM.
+ call.setIsInECBM(getEmergencyCallHelper()
+ .isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
+ if (mUserManager.isQuietModeEnabled(call.getUserHandleFromTargetPhoneAccount())
+ && !call.isEmergencyCall() && !call.isInECBM()) {
+ Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
+ phoneAccountHandle.getUserHandle());
+ call.setMissedReason(USER_MISSED_NOT_RUNNING);
+ call.setStartFailCause(CallFailureCause.INVALID_USE);
+ if (isConference) {
+ notifyCreateConferenceFailed(phoneAccountHandle, call);
+ } else {
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
+ }
+ }
+ else if (!isHandoverAllowed ||
(call.isSelfManaged() && !startFailCause.isSuccess())) {
if (isConference) {
notifyCreateConferenceFailed(phoneAccountHandle, call);
@@ -3412,10 +3430,11 @@
*/
boolean holdActiveCallForNewCall(Call call) {
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
- Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall);
+ Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
+ (activeCall == null ? "<none>" : activeCall.getId()));
if (activeCall != null && activeCall != call) {
if (canHold(activeCall)) {
- activeCall.hold();
+ activeCall.hold("swap to " + call.getId());
return true;
} else if (supportsHold(activeCall)
&& areFromSameSource(activeCall, call)) {
@@ -4900,12 +4919,25 @@
liveCallPhoneAccount);
}
- // First thing, if we are trying to make a call with the same phone account as the live
- // call, then allow it so that the connection service can make its own decision about
- // how to handle the new call relative to the current one.
+ // First thing, for managed calls, if we are trying to make a call with the same phone
+ // account as the live call, then allow it so that the connection service can make its own
+ // decision about how to handle the new call relative to the current one.
+ // Note: This behavior is primarily in place because Telephony historically manages the
+ // state of the calls it tracks by itself, holding and unholding as needed. Self-managed
+ // calls, even though from the same package are normally held/unheld automatically by
+ // Telecom. Calls within a single ConnectionService get held/unheld automatically during
+ // "swap" operations by CallsManager#holdActiveCallForNewCall. There is, however, a quirk
+ // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+ // would not work correctly because focus switches between ConnectionServices, yet we
+ // tended to assume that if the calls are from the same package that the hold/unhold should
+ // be done by the app. That was a bad assumption as it meant that we could have two active
+ // calls.
+ // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
- call.getTargetPhoneAccount())) {
- Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+ call.getTargetPhoneAccount())
+ && !call.isSelfManaged()
+ && !liveCall.isSelfManaged()) {
+ Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
call.getAnalytics().setCallIsAdditional(true);
liveCall.getAnalytics().setCallIsInterrupted(true);
return true;
@@ -5485,6 +5517,13 @@
impl.dump(pw);
pw.decreaseIndent();
}
+
+ if (mConnectionSvrFocusMgr != null) {
+ pw.println("mConnectionSvrFocusMgr:");
+ pw.increaseIndent();
+ mConnectionSvrFocusMgr.dump(pw);
+ pw.decreaseIndent();
+ }
}
/**
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 6fbc494..3694727 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -25,8 +25,10 @@
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.text.TextUtils;
+import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -41,6 +43,7 @@
public class ConnectionServiceFocusManager {
private static final String TAG = "ConnectionSvrFocusMgr";
private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
+ private final LocalLog mLocalLog = new LocalLog(20);
/** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
public interface ConnectionServiceFocusManagerFactory {
@@ -124,6 +127,11 @@
* @return {@code True} if this call can receive focus, {@code false} otherwise.
*/
boolean isFocusable();
+
+ /**
+ * @return the ID of the focusable for debug purposes.
+ */
+ String getId();
}
/** Interface define a call back for focus request event. */
@@ -361,10 +369,11 @@
}
private void updateCurrentFocusCall() {
+ CallFocus previousFocus = mCurrentFocusCall;
mCurrentFocusCall = null;
if (mCurrentFocus == null) {
- Log.d(this, "updateCurrentFocusCall: mCurrentFocus is null");
+ Log.i(this, "updateCurrentFocusCall: mCurrentFocus is null");
return;
}
@@ -377,11 +386,16 @@
for (CallFocus call : calls) {
if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
mCurrentFocusCall = call;
+ if (previousFocus != call) {
+ mLocalLog.log(call.getId());
+ }
Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
return;
}
}
-
+ if (previousFocus != null) {
+ mLocalLog.log("<none>");
+ }
Log.i(this, "updateCurrentFocusCall = null");
}
@@ -477,6 +491,11 @@
}
}
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Call Focus History:");
+ mLocalLog.dump(pw);
+ }
+
private final class FocusManagerHandler extends Handler {
FocusManagerHandler(Looper looper) {
super(looper);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cc5932c..5b727ab 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -919,6 +919,9 @@
callingPhoneAccountHandle.getComponentName().getPackageName());
}
+ boolean hasCrossUserAccess = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED;
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -930,7 +933,7 @@
// an emergency call.
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
- 0 /*excludedCapabilities*/, false);
+ 0 /*excludedCapabilities*/, hasCrossUserAccess);
PhoneAccountHandle phoneAccountHandle = null;
for (PhoneAccountHandle accountHandle : accountHandles) {
if(accountHandle.equals(callingPhoneAccountHandle)) {
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index a213e26..fbb666d 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -21,6 +21,8 @@
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
import com.android.internal.annotations.VisibleForTesting;
/**
@@ -34,6 +36,7 @@
private final DefaultDialerCache mDefaultDialerCache;
private final Timeouts.Adapter mTimeoutsAdapter;
private UserHandle mLocationPermissionGrantedToUser;
+ private PhoneAccountHandle mLastOutgoingEmergencyCallPAH;
//stores the original state of permissions that dialer had
private boolean mHadFineLocation = false;
@@ -46,6 +49,7 @@
private boolean mBackgroundLocationGranted = false;
private long mLastEmergencyCallTimestampMillis;
+ private long mLastOutgoingEmergencyCallTimestampMillis;
@VisibleForTesting
public EmergencyCallHelper(
@@ -63,7 +67,7 @@
grantLocationPermission(userHandle);
}
if (call != null && call.isEmergencyCall()) {
- recordEmergencyCallTime();
+ recordEmergencyCall(call);
}
}
@@ -78,15 +82,37 @@
return mLastEmergencyCallTimestampMillis;
}
- private void recordEmergencyCallTime() {
- mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+ void setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle) {
+ mLastOutgoingEmergencyCallPAH = accountHandle;
}
- private boolean isInEmergencyCallbackWindow() {
- return System.currentTimeMillis() - getLastEmergencyCallTimeMillis()
+ public boolean isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle) {
+ boolean ecbmActive = mLastOutgoingEmergencyCallPAH != null
+ && isInEmergencyCallbackWindow(mLastOutgoingEmergencyCallTimestampMillis)
+ && currentCallHandle != null
+ && currentCallHandle.equals(mLastOutgoingEmergencyCallPAH);
+ if (ecbmActive) {
+ Log.i(this, "ECBM is enabled for %s. The last recorded call timestamp was at %s",
+ currentCallHandle, mLastOutgoingEmergencyCallTimestampMillis);
+ }
+
+ return ecbmActive;
+ }
+
+ boolean isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis) {
+ return System.currentTimeMillis() - lastEmergencyCallTimestampMillis
< mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver());
}
+ private void recordEmergencyCall(Call call) {
+ mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+ if (!call.isIncoming()) {
+ // ECBM is applicable to MO emergency calls
+ mLastOutgoingEmergencyCallTimestampMillis = mLastEmergencyCallTimestampMillis;
+ mLastOutgoingEmergencyCallPAH = call.getTargetPhoneAccount();
+ }
+ }
+
private boolean shouldGrantTemporaryLocationPermission(Call call) {
if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
@@ -96,7 +122,8 @@
Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
return false;
}
- if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow()) {
+ if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow(
+ getLastEmergencyCallTimeMillis())) {
Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency");
return false;
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 3215605..3d3e3b4 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -338,7 +338,10 @@
UserHandle userToBind = getUserFromCall(call);
boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userToBind);
// Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
- // it separately to ensure that the ICS is bound to the appropriate user.
+ // it separately to ensure that the ICS is bound to the appropriate user. If ECBM is
+ // active, we know that a work sim was previously used to place a MO emergency call. We
+ // need to ensure that we bind to the CURRENT_USER in this case, as the work user would
+ // not be running (handled in getUserFromCall).
userToBind = isManagedProfile ? userToBind : UserHandle.CURRENT;
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
@@ -1191,6 +1194,11 @@
@Override
public void onCallAdded(Call call) {
UserHandle userFromCall = getUserFromCall(call);
+
+ Log.i(this, "onCallAdded: %s", call);
+ // Track the call if we don't already know about it.
+ addCall(call);
+
if (!isBoundAndConnectedToServices(userFromCall)) {
Log.i(this, "onCallAdded: %s; not bound or connected.", call);
// We are not bound, or we're not connected.
@@ -1206,10 +1214,6 @@
mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
userFromCall);
- Log.i(this, "onCallAdded: %s", call);
- // Track the call if we don't already know about it.
- addCall(call);
-
if (inCallServiceConnection != null) {
Log.i(this, "mInCallServiceConnection isConnected=%b",
inCallServiceConnection.isConnected());
@@ -2600,7 +2604,8 @@
UserManager userManager = mContext.getSystemService(UserManager.class);
// Emergency call should never be blocked, so if the user associated with call is in
// quite mode, use the primary user for the emergency call.
- if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+ if ((call.isEmergencyCall() || call.isInECBM())
+ && userManager.isQuietModeEnabled(userFromCall)) {
return mCallsManager.getCurrentUserHandle();
}
return userFromCall;
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 8ce5dc3..4e7546f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -225,6 +225,8 @@
public static final String FLASH_NOTIFICATION_STOP = "FLASH_NOTIFICATION_STOP";
public static final String GAINED_FGS_DELEGATION = "GAINED_FGS_DELEGATION";
public static final String LOST_FGS_DELEGATION = "LOST_FGS_DELEGATION";
+ public static final String START_STREAMING = "START_STREAMING";
+ public static final String STOP_STREAMING = "STOP_STREAMING";
public static class Timings {
public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 1e6403e..d83e551 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -425,6 +425,15 @@
@Override
public void onError(CallException exception) {
+ if (isAnswerRequest) {
+ // This also sends the signal to untrack from TSW and the client_TSW
+ removeCallFromCallsManager(call,
+ new DisconnectCause(DisconnectCause.REJECTED,
+ "client rejected to answer the call;"
+ + " force disconnecting"));
+ } else {
+ mCallsManager.markCallAsOnHold(call);
+ }
maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
}
});
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index 8b4ffed..93d9836 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -128,6 +128,8 @@
boolean success = latch.await(VoipCallTransaction.TIMEOUT_LIMIT, TimeUnit.MILLISECONDS);
if (!success) {
// client send onError and failed to complete transaction
+ Log.i(TAG, String.format("CallEventCallbackAckTransaction:"
+ + " client failed to complete the [%s] transaction", mAction));
return CompletableFuture.completedFuture(TRANSACTION_FAILED);
} else {
// success
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index 98faf3d..773dfb8 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Queue;
public class TransactionManager {
@@ -63,30 +65,31 @@
OutcomeReceiver<VoipCallTransactionResult, CallException> receiver) {
synchronized (sLock) {
mTransactions.add(transaction);
- transaction.setCompleteListener(new TransactionCompleteListener() {
- @Override
- public void onTransactionCompleted(VoipCallTransactionResult result,
- String transactionName){
- Log.i(TAG, String.format("transaction completed: with result=[%d]",
- result.getResult()));
- if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
- receiver.onResult(result);
- } else {
- receiver.onError(
- new CallException(result.getMessage(),
- result.getResult()));
- }
- finishTransaction();
- }
-
- @Override
- public void onTransactionTimeout(String transactionName){
- receiver.onError(new CallException(transactionName + " timeout",
- CODE_OPERATION_TIMED_OUT));
- finishTransaction();
- }
- });
}
+ transaction.setCompleteListener(new TransactionCompleteListener() {
+ @Override
+ public void onTransactionCompleted(VoipCallTransactionResult result,
+ String transactionName){
+ Log.i(TAG, String.format("transaction completed: with result=[%d]",
+ result.getResult()));
+ if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+ receiver.onResult(result);
+ } else {
+ receiver.onError(
+ new CallException(result.getMessage(),
+ result.getResult()));
+ }
+ finishTransaction();
+ }
+
+ @Override
+ public void onTransactionTimeout(String transactionName){
+ receiver.onError(new CallException(transactionName + " timeout",
+ CODE_OPERATION_TIMED_OUT));
+ finishTransaction();
+ }
+ });
+
startTransactions();
}
@@ -102,8 +105,8 @@
return;
}
mCurrentTransaction = mTransactions.poll();
- mCurrentTransaction.start();
}
+ mCurrentTransaction.start();
}
private void finishTransaction() {
@@ -115,10 +118,12 @@
@VisibleForTesting
public void clear() {
+ List<VoipCallTransaction> pendingTransactions;
synchronized (sLock) {
- for (VoipCallTransaction transaction : mTransactions) {
- transaction.finish();
- }
+ pendingTransactions = new ArrayList<>(mTransactions);
+ }
+ for (VoipCallTransaction transaction : pendingTransactions) {
+ transaction.finish();
}
}
}
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
index d0304a9..2a81051 100644
--- a/src/com/android/server/telecom/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -207,8 +207,10 @@
synchronized (mLock) {
Log.i(this, "stopFGSDelegation of handle %s", handle);
Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
- for (Call call : calls) {
- stopMonitorWorks(call);
+ if (calls != null) {
+ for (Call call : calls) {
+ stopMonitorWorks(call);
+ }
}
mPhoneAccountHandleListMap.remove(handle);
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index dd8258a..645a42b 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -259,6 +259,15 @@
</intent-filter>
</service>
+ <service android:name="com.android.server.telecom.testapps.OtherSelfManagedConnectionService"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+ android:exported="true">
+ <intent-filter>
+ <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"/>
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index d26d629..98b879a 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -55,6 +55,12 @@
android:layout_height="wrap_content"
android:background="@color/test_call_b_color"
android:text="2"/>
+ <RadioButton
+ android:id="@+id/useAcct3Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/test_call_c_color"
+ android:text="3"/>
</RadioGroup>
<TextView
android:id="@+id/hasFocus"
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
index 3939e78..9447ac8 100644
--- a/testapps/res/values/colors.xml
+++ b/testapps/res/values/colors.xml
@@ -17,4 +17,5 @@
<resources>
<color name="test_call_a_color">#f2eebf</color>
<color name="test_call_b_color">#afc5e6</color>
+ <color name="test_call_c_color">#c5afe6</color>
</resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
new file mode 100644
index 0000000..7bb9830
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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;
+
+public class OtherSelfManagedConnectionService extends SelfManagedConnectionService {
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index d4661ff..273b060 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,20 +46,27 @@
public static String SELF_MANAGED_ACCOUNT_1 = "1";
public static String SELF_MANAGED_ACCOUNT_2 = "2";
+ public static String SELF_MANAGED_ACCOUNT_1A = "1A";
public static String SELF_MANAGED_ACCOUNT_3 = "3";
public static String SELF_MANAGED_NAME_1 = "SuperCall";
public static String SELF_MANAGED_NAME_2 = "Mega Call";
- public static String SELF_MANAGED_NAME_3 = "SM Call";
+ public static String SELF_MANAGED_NAME_1A = "SM Call";
+ public static String SELF_MANAGED_NAME_3 = "Sep Process";
public static String CUSTOM_URI_SCHEME = "custom";
private static SelfManagedCallList sInstance;
private static ComponentName COMPONENT_NAME = new ComponentName(
SelfManagedCallList.class.getPackage().getName(),
SelfManagedConnectionService.class.getName());
+ private static ComponentName OTHER_COMPONENT_NAME = new ComponentName(
+ SelfManagedCallList.class.getPackage().getName(),
+ OtherSelfManagedConnectionService.class.getName());
private static Uri SELF_MANAGED_ADDRESS_1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, "555-1212",
"");
private static Uri SELF_MANAGED_ADDRESS_2 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
"me@test.org", "");
+ private static Uri SELF_MANAGED_ADDRESS_3 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
+ "hilda@test.org", "");
private static Map<String, PhoneAccountHandle> mPhoneAccounts = new ArrayMap();
public static SelfManagedCallList getInstance() {
@@ -101,20 +108,29 @@
SELF_MANAGED_NAME_1, true /* areCallsLogged */);
registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
SELF_MANAGED_NAME_2, false /* areCallsLogged */);
- registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_3, SELF_MANAGED_ADDRESS_1,
- SELF_MANAGED_NAME_3, true /* areCallsLogged */);
+ registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+ SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+ registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+ SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+ registerPhoneAccount(context, OTHER_COMPONENT_NAME, SELF_MANAGED_ACCOUNT_3,
+ SELF_MANAGED_ADDRESS_3, SELF_MANAGED_NAME_3, false /* areCallsLogged */);
}
public void registerPhoneAccount(Context context, String id, Uri address, String name,
- boolean areCallsLogged) {
- PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
+ boolean areCallsLogged) {
+ registerPhoneAccount(context, COMPONENT_NAME, id, address, name, areCallsLogged);
+ }
+
+ public void registerPhoneAccount(Context context, ComponentName componentName, String id,
+ Uri address, String name, boolean areCallsLogged) {
+ PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
mPhoneAccounts.put(id, handle);
Bundle extras = new Bundle();
extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
if (areCallsLogged) {
extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
}
- if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+ if (id.equals(SELF_MANAGED_ACCOUNT_1A)) {
extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
}
PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 75ceb62..475f255 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -166,8 +166,10 @@
SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
- } else {
+ } else if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2)) {
result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+ } else {
+ result.setBackgroundColor(result.getContext().getColor(R.color.test_call_c_color));
}
CallAudioState audioState = connection.getCallAudioState();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 44410d2..5cdaf3d 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -43,8 +43,6 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.android.server.telecom.testapps.R;
-
import java.util.Objects;
/**
@@ -66,6 +64,7 @@
private Button mDisableCarMode;
private RadioButton mUseAcct1Button;
private RadioButton mUseAcct2Button;
+ private RadioButton mUseAcct3Button;
private CheckBox mHoldableCheckbox;
private CheckBox mVideoCallCheckbox;
private EditText mNumber;
@@ -165,6 +164,7 @@
}));
mUseAcct1Button = findViewById(R.id.useAcct1Button);
mUseAcct2Button = findViewById(R.id.useAcct2Button);
+ mUseAcct3Button = findViewById(R.id.useAcct3Button);
mHasFocus = findViewById(R.id.hasFocus);
mVideoCallCheckbox = findViewById(R.id.videoCall);
mHoldableCheckbox = findViewById(R.id.holdable);
@@ -183,6 +183,8 @@
return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1);
} else if (mUseAcct2Button.isChecked()) {
return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2);
+ } else if (mUseAcct3Button.isChecked()) {
+ return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
}
return null;
}
@@ -214,8 +216,7 @@
private void placeSelfManagedOutgoingCall() {
TelecomManager tm = TelecomManager.from(this);
- PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
- SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+ PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
@@ -264,7 +265,7 @@
private void placeSelfManagedIncomingCall() {
TelecomManager tm = TelecomManager.from(this);
PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
- SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+ SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
if (mCheckIfPermittedBeforeCalling.isChecked()) {
if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 6670095..3ef8fbb 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -142,11 +142,6 @@
connection.setVideoState(request.getVideoState());
Log.i(this, "createSelfManagedConnection %s", connection);
mCallList.addConnection(connection);
- try {
- Thread.sleep(8000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
return connection;
}
}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
index 4c9f52d..b868b70 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -254,14 +254,14 @@
new OutcomeReceiver<Void, CallException>() {
@Override
public void onResult(Void result) {
- Log.i(TAG, String.format("success w/ %s", tag));
+ Log.i(TAG, String.format("requestEndpointChange: success w/ %s", tag));
updateCurrentEndpointWithOnResult(endpoint);
}
@Override
public void onError(CallException e) {
- Log.i(TAG, String.format("%s :failed to switch to endpoint=[%s],"
- + " due to exception=[%s]", tag, endpoint, e.toString()));
+ Log.i(TAG, String.format("requestEndpointChange: %s failed to switch to "
+ + "endpoint=[%s] due to exception=[%s]", tag, endpoint, e));
}
});
}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 453450d..9047da3 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -44,6 +44,7 @@
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
import android.telecom.CallAudioState;
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 0f38ca5..d0a1d8b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -131,7 +131,7 @@
assertEquals(CallAudioModeStateMachine.STREAMING_STATE_NAME, sm.getCurrentStateName());
verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
- verify(mAudioManager).setMode(eq(AudioManager.MODE_CALL_REDIRECT));
+ verify(mAudioManager).setMode(eq(AudioManager.MODE_COMMUNICATION_REDIRECT));
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index fc4e935..22a850f 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.tests;
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
+
import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.fail;
@@ -58,7 +60,9 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.BlockedNumberContract;
+import android.provider.Telephony;
import android.telecom.CallException;
import android.telecom.CallScreeningService;
import android.telecom.CallerInfo;
@@ -166,6 +170,8 @@
ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.baz/.Self"), "Self");
+ private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
.setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
| PhoneAccount.CAPABILITY_CALL_PROVIDER
@@ -190,6 +196,11 @@
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
.setIsEnabled(true)
.build();
+ private static final PhoneAccount SELF_MANAGED_2_ACCOUNT = new PhoneAccount.Builder(
+ SELF_MANAGED_2_HANDLE, "Self2")
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ .setIsEnabled(true)
+ .build();
private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
@@ -818,7 +829,7 @@
mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
// THEN the ongoing call is held and the focus request for incoming call is sent
- verify(ongoingCall).hold();
+ verify(ongoingCall).hold(anyString());
verifyFocusRequestAndExecuteCallback(incomingCall);
// and the incoming call is answered.
@@ -1026,7 +1037,7 @@
mCallsManager.markCallAsActive(newCall);
// THEN the ongoing call is held
- verify(ongoingCall).hold();
+ verify(ongoingCall).hold(anyString());
verifyFocusRequestAndExecuteCallback(newCall);
// and the new call is active
@@ -1683,6 +1694,57 @@
assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
}
+ /**
+ * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+ * active call. This assumes same connection service in the same app.
+ */
+ @SmallTest
+ @Test
+ public void testMakeRoomForOutgoingCallForSameVoipApp() {
+ Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+ CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+ 0 /* properties */);
+ Call newDialingCall = createCall(SELF_MANAGED_HANDLE, CallState.DIALING);
+ newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+ | Connection.CAPABILITY_SUPPORT_HOLD);
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+ verify(activeCall).hold(anyString());
+ }
+
+ /**
+ * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+ * active call. This assumes different connection services in the same app.
+ */
+ @SmallTest
+ @Test
+ public void testMakeRoomForOutgoingCallForSameVoipAppDifferentConnectionService() {
+ Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+ CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+ 0 /* properties */);
+ Call newDialingCall = createCall(SELF_MANAGED_2_HANDLE, CallState.DIALING);
+ newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+ | Connection.CAPABILITY_SUPPORT_HOLD);
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+ verify(activeCall).hold(anyString());
+ }
+
+ /**
+ * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+ * active call. This assumes different connection services in the same app.
+ */
+ @SmallTest
+ @Test
+ public void testMakeRoomForOutgoingCallForSameNonVoipApp() {
+ Call activeCall = addSpyCall(SIM_1_HANDLE, null /* connMgr */,
+ CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+ 0 /* properties */);
+ Call newDialingCall = createCall(SIM_1_HANDLE, CallState.DIALING);
+ newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+ | Connection.CAPABILITY_SUPPORT_HOLD);
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+ verify(activeCall, never()).hold(anyString());
+ }
+
@SmallTest
@Test
public void testMakeRoomForOutgoingCallHasOutgoingCallSelectingAccount() {
@@ -2357,6 +2419,65 @@
assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
}
+ @SmallTest
+ @Test
+ public void testRejectIncomingCallOnPAHInactive() throws Exception {
+ ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+ doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+ mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+ SIM_2_HANDLE.getUserHandle(), service);
+
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+ Call newCall = mCallsManager.processIncomingCallIntent(
+ SIM_2_HANDLE, new Bundle(), false);
+
+ verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+ assertFalse(newCall.isInECBM());
+ assertEquals(USER_MISSED_NOT_RUNNING, newCall.getMissedReason());
+ }
+
+ @SmallTest
+ @Test
+ public void testAcceptIncomingCallOnPAHInactiveAndECBMActive() throws Exception {
+ ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+ doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+ mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+ SIM_2_HANDLE.getUserHandle(), service);
+
+ when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(SIM_2_HANDLE)))
+ .thenReturn(true);
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+ Call newCall = mCallsManager.processIncomingCallIntent(
+ SIM_2_HANDLE, new Bundle(), false);
+
+ assertTrue(newCall.isInECBM());
+ verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+ }
+
+ @SmallTest
+ @Test
+ public void testAcceptIncomingEmergencyCallOnPAHInactive() throws Exception {
+ ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+ doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+ mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+ SIM_2_HANDLE.getUserHandle(), service);
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, TEST_ADDRESS);
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+ when(tm.isEmergencyNumber(any(String.class))).thenReturn(true);
+ Call newCall = mCallsManager.processIncomingCallIntent(
+ SIM_2_HANDLE, extras, false);
+
+ assertFalse(newCall.isInECBM());
+ assertTrue(newCall.isEmergencyCall());
+ verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+ }
+
public class LatchedOutcomeReceiver implements OutcomeReceiver<Boolean,
CallException> {
CountDownLatch mCountDownLatch;
@@ -2968,6 +3089,10 @@
mClockProxy,
mToastFactory);
ongoingCall.setState(initialState, "just cuz");
+ if (targetPhoneAccount == SELF_MANAGED_HANDLE
+ || targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
+ ongoingCall.setIsSelfManaged(true);
+ }
return ongoingCall;
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index e61bc2a..16fd630 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -595,6 +595,34 @@
@MediumTest
@Test
public void
+ testBindToService_UserAssociatedWithCallIsInQuietMode_NonEmergCallECBM_BindsToPrimaryUser()
+ throws Exception {
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ when(mMockCall.isInECBM()).thenReturn(true);
+ when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ }
+
+ @MediumTest
+ @Test
+ public void
testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -1002,6 +1030,107 @@
}
/**
+ * Tests a case where InCallController DOES NOT bind to ANY InCallServices when the call is
+ * first added, but then one becomes available after the call starts. This test was originally
+ * added to reproduce a bug which would cause the call id mapper in the InCallController to not
+ * track a newly added call unless something was bound when the call was first added.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testNoInitialBinding() throws Exception {
+ Bundle callExtras = new Bundle();
+ callExtras.putBoolean("whatever", true);
+
+ // Make a basic call
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+ when(mMockCall.isIncoming()).thenReturn(false);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+ when(mMockCall.isExternalCall()).thenReturn(false);
+ when(mMockCall.isSelfManaged()).thenReturn(true);
+ when(mMockCall.visibleToInCallService()).thenReturn(true);
+
+ // Dialer doesn't handle these calls, but non-UI ICS does.
+ when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+ .thenReturn(DEF_PKG);
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+ eq(serviceBindingFlags),
+ eq(mUserHandle))).thenReturn(true);
+ when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+ .thenReturn(300_000L);
+
+ // Setup package manager; there is a dialer and disable non-ui ICS
+ when(mMockPackageManager.queryIntentServicesAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(
+ Arrays.asList(
+ getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+ getNonUiResolveinfo(true /* selfManaged */,
+ false /* isEnabled */)
+ )
+ );
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+ // Add the call.
+ mInCallController.onCallAdded(mMockCall);
+
+ // There will be 4 calls for the various types of ICS; this is normal.
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+ any(Intent.class),
+ eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+ eq(CURRENT_USER_ID));
+
+ // Verify no bind at this point
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, never()).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+
+ // Setup mocks to enable non-ui ICS
+ when(mMockPackageManager.queryIntentServicesAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(
+ Arrays.asList(
+ getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+ getNonUiResolveinfo(true /* selfManaged */,
+ true /* isEnabled */)
+ )
+ );
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ // Emulate a late enable of the non-ui ICS
+ Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+ packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ new String[] {NONUI_CLASS});
+ packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+ mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+ // Make sure we bound to it.
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+ }
+
+ /**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
* supports external calls.
*/