Merge "Improve the performance to avoid the potential ANR" into main
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 8c2f631..d156c0c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -1101,6 +1101,9 @@
call.getId());
disconnectedToneFuture.complete(null);
}
+ // Make sure we schedule the unbinding of the BT ICS once the disconnected tone future has
+ // been completed.
+ mCallsManager.getInCallController().maybeScheduleBtUnbind(call);
}
@VisibleForTesting
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 7358be8..98adae9 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -879,24 +879,22 @@
mFocusType = focus;
switch (focus) {
case NO_FOCUS -> {
- if (mIsActive) {
- // Notify the CallAudioModeStateMachine that audio operations are complete so
- // that we can relinquish audio focus.
- mCallAudioManager.notifyAudioOperationsComplete();
-
- // Reset mute state after call ends.
- handleMuteChanged(false);
- // Ensure we reset call audio state at the end of the call (i.e. if we're on
- // speaker, route back to earpiece). If we're on BT, remain on BT if it's still
- // connected.
- AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()
- ? calculateBaselineRoute(false, true, null)
- : mCurrentRoute;
- routeTo(false, route);
- // Clear pending messages
- mPendingAudioRoute.clearPendingMessages();
- clearRingingBluetoothAddress();
- }
+ // Notify the CallAudioModeStateMachine that audio operations are complete so
+ // that we can relinquish audio focus.
+ mCallAudioManager.notifyAudioOperationsComplete();
+ // Reset mute state after call ends. This should remain unaffected if audio routing
+ // never went active.
+ handleMuteChanged(false);
+ // Ensure we reset call audio state at the end of the call (i.e. if we're on
+ // speaker, route back to earpiece). If we're on BT, remain on BT if it's still
+ // connected.
+ AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()
+ ? calculateBaselineRoute(false, true, null)
+ : mCurrentRoute;
+ routeTo(false, route);
+ // Clear pending messages
+ mPendingAudioRoute.clearPendingMessages();
+ clearRingingBluetoothAddress();
}
case ACTIVE_FOCUS -> {
// Route to active baseline route (we may need to change audio route in the case
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index bda5063..3f8f579 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -305,7 +305,7 @@
//this is really used for cases where the userhandle for a call
//does not match what we want to use for bindAsUser
- private final UserHandle mUserHandleToUseForBinding;
+ private UserHandle mUserHandleToUseForBinding;
public InCallServiceBindingConnection(InCallServiceInfo info) {
mInCallServiceInfo = info;
@@ -388,6 +388,8 @@
+ "INTERACT_ACROSS_USERS permission");
}
}
+ // Used for referencing what user we used to bind to the given ICS.
+ mUserHandleToUseForBinding = userToBind;
Log.i(this, "using user id: %s for binding. User from Call is: %s", userToBind,
userFromCall);
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
@@ -1230,7 +1232,7 @@
mCombinedInCallServiceMap = new ArrayMap<>();
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
- private final Collection<Call> mPendingEndToneCall = new ArraySet<>();
+ private final Collection<Call> mBtIcsCallTracker = new ArraySet<>();
private final Context mContext;
private final AppOpsManager mAppOpsManager;
@@ -1246,7 +1248,7 @@
mInCallServiceConnections = new ArrayMap<>();
private final Map<UserHandle, NonUIInCallServiceConnectionCollection>
mNonUIInCallServiceConnections = new ArrayMap<>();
- private final Map<UserHandle, InCallServiceConnection> mBTInCallServiceConnections =
+ private final Map<UserHandle, InCallServiceBindingConnection> mBTInCallServiceConnections =
new ArrayMap<>();
private final ClockProxy mClockProxy;
private final IBinder mToken = new Binder();
@@ -1421,6 +1423,7 @@
bindingToBtRequired = true;
bindToBTService(call, null);
}
+
if (!isBoundAndConnectedToServices(userFromCall)) {
Log.i(this, "onCallAdded: %s; not bound or connected to other ICS.", call);
// We are not bound, or we're not connected.
@@ -1565,47 +1568,85 @@
+ "disconnected tone future");
mDisconnectedToneBtFutures.get(call.getId()).complete(null);
}
- mPendingEndToneCall.remove(call);
- if (!mPendingEndToneCall.isEmpty()) {
- return;
- }
- UserHandle userHandle = getUserFromCall(call);
- if (mBTInCallServiceConnections.containsKey(userHandle)) {
- Log.i(this, "onDisconnectedTonePlaying: Schedule unbind BT service");
- final InCallServiceConnection connection =
- mBTInCallServiceConnections.get(userHandle);
-
- // Similar to in onCallRemoved when we unbind from the other ICS, we need to
- // delay unbinding from the BT ICS because we need to give the ICS a
- // moment to finish the onCallRemoved signal it got just prior.
- mHandler.postDelayed(new Runnable("ICC.oDCTP", mLock) {
- @Override
- public void loggedRun() {
- Log.i(this, "onDisconnectedTonePlaying: unbinding from BT ICS.");
- // Prevent unbinding in the case that this is run while another call
- // has been placed/received. Otherwise, we will early unbind from
- // the BT ICS and not be able to properly relay call state updates.
- if (!mBTInCallServiceConnections.containsKey(userHandle)) {
- connection.disconnect();
- } else {
- Log.i(this, "onDisconnectedTonePlaying: Refraining from "
- + "unbinding BT ICS. Another call is ongoing.");
- }
- }
- }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
- mContext.getContentResolver()));
-
- mBTInCallServiceConnections.remove(userHandle);
- }
- // Ensure that BT ICS instance is cleaned up
- if (mBTInCallServices.remove(userHandle) != null) {
- updateCombinedInCallServiceMap(userHandle);
- }
+ // Schedule unbinding of BT ICS.
+ maybeScheduleBtUnbind(call);
}
}
}
}
+ public void maybeScheduleBtUnbind(Call call) {
+ mBtIcsCallTracker.remove(call);
+ // Track the current calls that are being tracked by the BT ICS and determine the
+ // associated users of those calls as well as the users which have been used to bind to the
+ // ICS.
+ Set<UserHandle> usersFromOngoingCalls = new ArraySet<>();
+ Set<UserHandle> usersCurrentlyBound = new ArraySet<>();
+ for (Call pendingCall : mBtIcsCallTracker) {
+ UserHandle userFromPendingCall = getUserFromCall(pendingCall);
+ final InCallServiceBindingConnection pendingCallConnection =
+ mBTInCallServiceConnections.get(userFromPendingCall);
+ usersFromOngoingCalls.add(userFromPendingCall);
+ if (pendingCallConnection != null) {
+ usersCurrentlyBound.add(pendingCallConnection.mUserHandleToUseForBinding);
+ }
+ }
+
+ UserHandle userHandle = getUserFromCall(call);
+ // Refrain from unbinding ICS and clearing the ICS mapping if there's an ongoing call under
+ // the same associated user. Make sure we keep the internal mappings so that they aren't
+ // cleared until that call is disconnected. Note here that if the associated users are the
+ // same, the user used for the binding will also be the same.
+ if (usersFromOngoingCalls.contains(userHandle)) {
+ Log.i(this, "scheduleBtUnbind: Refraining from unbinding BT service due to an ongoing "
+ + "call detected under the same user (%s).", userHandle);
+ return;
+ }
+
+ if (mBTInCallServiceConnections.containsKey(userHandle)) {
+ Log.i(this, "scheduleBtUnbind: Schedule unbind BT service");
+ final InCallServiceBindingConnection connection =
+ mBTInCallServiceConnections.get(userHandle);
+ // The user that was used for binding may be different than the user from call
+ // (associated user), which is what we use to reference the BT ICS bindings. For
+ // example, consider the work profile scenario where the BT ICS is only available under
+ // User 0: in this case, the user to bind to will be User 0 whereas we store the
+ // references to this connection and BT ICS under the work user. This logic ensures
+ // that we prevent unbinding the BT ICS if there is a personal (associatedUser: 0) call
+ // + work call (associatedUser: 10) and one of them gets disconnected.
+ if (usersCurrentlyBound.contains(connection.mUserHandleToUseForBinding)) {
+ Log.i(this, "scheduleBtUnbind: Refraining from unbinding BT service to an "
+ + "ongoing call detected which is bound to the same user (%s).",
+ connection.mUserHandleToUseForBinding);
+ } else {
+ // Similar to in onCallRemoved when we unbind from the other ICS, we need to
+ // delay unbinding from the BT ICS because we need to give the ICS a
+ // moment to finish the onCallRemoved signal it got just prior.
+ mHandler.postDelayed(new Runnable("ICC.sBU", mLock) {
+ @Override
+ public void loggedRun() {
+ Log.i(this, "onDisconnectedTonePlaying: unbinding from BT ICS.");
+ // Prevent unbinding in the case that this is run while another call
+ // has been placed/received. Otherwise, we will early unbind from
+ // the BT ICS and not be able to properly relay call state updates.
+ if (!mBTInCallServiceConnections.containsKey(userHandle)) {
+ connection.disconnect();
+ } else {
+ Log.i(this, "onDisconnectedTonePlaying: Refraining from "
+ + "unbinding BT ICS. Another call is ongoing.");
+ }
+ }
+ }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
+ mContext.getContentResolver()));
+ }
+ mBTInCallServiceConnections.remove(userHandle);
+ }
+ // Ensure that BT ICS instance is cleaned up
+ if (mBTInCallServices.remove(userHandle) != null) {
+ updateCombinedInCallServiceMap(userHandle);
+ }
+ }
+
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
@@ -2841,7 +2882,7 @@
mCallIdMapper.addCall(call);
call.addListener(mCallListener);
if (mFeatureFlags.separatelyBindToBtIncallService()) {
- mPendingEndToneCall.add(call);
+ mBtIcsCallTracker.add(call);
}
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c309dd5..12778b0 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -358,6 +358,12 @@
mVolumeShaperConfig = null;
+ String vibratorAttrs = String.format("hasVibrator=%b, userRequestsVibrate=%b, "
+ + "ringerMode=%d, isVibratorEnabled=%b",
+ mVibrator.hasVibrator(),
+ mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+ mAudioManager.getRingerMode(), isVibratorEnabled);
+
if (attributes.isRingerAudible()) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
@@ -404,6 +410,8 @@
}
} else {
foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+ vibratorAttrs);
return attributes.shouldAcquireAudioFocus(); // ringer not audible
}
}
@@ -436,11 +444,7 @@
if (!vibratorReserved) {
foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
- "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, "
- + "isVibratorEnabled=%b",
- mVibrator.hasVibrator(),
- mSystemSettingsUtil.isRingVibrationEnabled(mContext),
- mAudioManager.getRingerMode(), isVibratorEnabled);
+ vibratorAttrs);
}
// The vibration logic depends on the loaded ringtone, but we need to defer the ringtone
@@ -556,6 +560,11 @@
mIsVibrating = true;
mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
Log.i(this, "start vibration.");
+ } else {
+ Log.i(this, "vibrateIfNeeded: skip; isVibrating=%b, fgCallId=%s, vibratingCall=%s",
+ mIsVibrating,
+ (foregroundCall == null ? "null" : foregroundCall.getId()),
+ (mVibratingCall == null ? "null" : mVibratingCall.getId()));
}
// else stopped already: this isn't started unless a reservation was made.
}
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index ded4d9c..55326e8 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -204,8 +204,8 @@
pw.print("(override ");
pw.print(mOverrideDefaultCallRedirectionApp);
pw.print(") ");
- pw.print(getRoleManagerCallRedirectionApp(Binder.getCallingUserHandle()));
}
+ pw.print(getRoleManagerCallRedirectionApp(Binder.getCallingUserHandle()));
pw.println();
pw.print("DefaultCallScreeningApp: ");
@@ -213,19 +213,19 @@
pw.print("(override ");
pw.print(mOverrideDefaultCallScreeningApp);
pw.print(") ");
- pw.print(getRoleManagerCallScreeningApp(Binder.getCallingUserHandle()));
}
+ pw.print(getRoleManagerCallScreeningApp(Binder.getCallingUserHandle()));
pw.println();
pw.print("DefaultCallCompanionApps: ");
- if (mOverrideCallCompanionApps != null) {
+ if (!mOverrideCallCompanionApps.isEmpty()) {
pw.print("(override ");
pw.print(mOverrideCallCompanionApps.stream().collect(Collectors.joining(", ")));
pw.print(") ");
- List<String> appsInRole = getRoleManagerCallCompanionApps();
- if (appsInRole != null) {
- pw.print(appsInRole.stream().collect(Collectors.joining(", ")));
- }
+ }
+ List<String> appsInRole = getRoleManagerCallCompanionApps();
+ if (!appsInRole.isEmpty()) {
+ pw.print(appsInRole.stream().collect(Collectors.joining(", ")));
}
pw.println();
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 7ec2687..a662dde 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -892,8 +892,7 @@
PhoneAccount.CAPABILITY_CONNECTION_MANAGER) ||
account.hasCapabilities(
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- throw new SecurityException("Self-managed ConnectionServices and "
- + "transactional voip apps "
+ throw new SecurityException("Self-managed ConnectionServices "
+ "cannot also be call capable, connection managers, or "
+ "SIM accounts.");
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 330e84c..ba48c64 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -1099,6 +1099,38 @@
any(CallAudioState.class), eq(expectedState));
}
+ @Test
+ @SmallTest
+ public void testAbandonCallAudioFocusAfterCallEnd() {
+ // Make sure in-band ringing is disabled so that route never becomes active
+ when(mBluetoothRouteManager.isInbandRingEnabled(eq(BLUETOOTH_DEVICE_1))).thenReturn(false);
+
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ assertFalse(mController.isActive());
+
+ // Verify route never went active due to in-band ringing being disabled.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
+ assertFalse(mController.isActive());
+
+ // Emulate scenario of rejecting an incoming call so that call focus is lost and verify
+ // that we abandon the call audio focus that was gained from when the call went to
+ // ringing state.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
+ // Ensure we tell the CallAudioManager that audio operations are done so that we can ensure
+ // audio focus is relinquished.
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).notifyAudioOperationsComplete();
+ }
+
private void verifyConnectBluetoothDevice(int audioType) {
mController.initialize();
mController.setActive(true);