Merge "DSDA: Handle call resume failure" into main
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 8416533..ce06d55 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -849,13 +849,15 @@
private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type,
String deviceAddress) {
AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress);
- if (bluetoothRoute != null) {
+ boolean isBtDeviceCurrentActive = Objects.equals(bluetoothRoute,
+ getArbitraryBluetoothDevice());
+ if (bluetoothRoute != null && isBtDeviceCurrentActive) {
Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
mIsActive);
routeTo(mIsActive, bluetoothRoute);
} else {
- Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
- type, deviceAddress);
+ Log.i(this, "request to route to unavailable bluetooth route or the route isn't the "
+ + "currently active device - type (%s), address (%s)", type, deviceAddress);
}
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 3f8f579..6cfa4fd 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -44,6 +44,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.PackageTagsList;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -1723,7 +1724,8 @@
try {
inCallService.updateCall(
- sanitizeParcelableCallForService(info, parcelableCall));
+ copyIfLocal(sanitizeParcelableCallForService(info, parcelableCall),
+ inCallService));
} catch (RemoteException ignored) {
}
}
@@ -2854,7 +2856,8 @@
ParcelableCall parcelableCall, ComponentName componentName) {
try {
inCallService.updateCall(
- sanitizeParcelableCallForService(info, parcelableCall));
+ copyIfLocal(sanitizeParcelableCallForService(info, parcelableCall),
+ inCallService));
} catch (RemoteException exception) {
Log.w(this, "Call status update did not send to: "
+ componentName + " successfully with error " + exception);
@@ -3435,4 +3438,43 @@
}
return false;
}
+
+ /**
+ * Given a {@link ParcelableCall} and a {@link IInCallService}, determines if the ICS binder is
+ * local or remote. If the binder is remote, we just return the parcelable call instance
+ * already constructed.
+ * If the binder if local, as will be the case for
+ * {@code EnhancedConfirmationCallTrackerService} (or any other ICS in the system server, the
+ * underlying Binder implementation is NOT going to parcel and unparcel the
+ * {@link ParcelableCall} instance automatically. This means that the parcelable call instance
+ * is passed by reference and that the ICS in the system server could potentially try to access
+ * internals in the {@link ParcelableCall} in an unsafe manner. As a workaround, we will
+ * manually parcel and unparcel the {@link ParcelableCall} instance so that they get a fresh
+ * copy that they can use safely.
+ *
+ * @param parcelableCall The ParcelableCall instance we want to maybe copy.
+ * @param remote the binder the call is going out over.
+ * @return either the original {@link ParcelableCall} or a deep copy of it if the destination
+ * binder is local.
+ */
+ private ParcelableCall copyIfLocal(ParcelableCall parcelableCall, IInCallService remote) {
+ // We care more about parceling than local (though they should be the same); so, use
+ // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+ if (remote.asBinder().queryLocalInterface(IInCallService.Stub.DESCRIPTOR) == null) {
+ // No local interface, so binder itself will parcel and thus we don't need to.
+ return parcelableCall;
+ }
+ // Binder won't be parceling; however, the remotes assume they have their own native
+ // objects (and don't know if caller is local or not), so we need to make a COPY here so
+ // that the remote can clean it up without clearing the original transaction.
+ // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+ final Parcel p = Parcel.obtain();
+ try {
+ parcelableCall.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return ParcelableCall.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 9daa7cf..1b1ca56 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -1357,6 +1357,56 @@
verify(mCallAudioManager, timeout(TEST_TIMEOUT)).notifyAudioOperationsComplete();
}
+ @Test
+ @SmallTest
+ public void testActiveDevicePresentRoutesOnCurrentActive() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ // Connect first BT device.
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ // Connect another BT device.
+ String scoDeviceAddress = "00:00:00:00:00:03";
+ BluetoothDevice scoDevice2 =
+ BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+ BLUETOOTH_DEVICES.add(scoDevice2);
+
+ // Signal second BT device added in controller and verify routing to that device upon
+ // receiving active focus.
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ scoDevice2);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Update the currently tracked active device to be BLUETOOTH_DEVICE_1.
+ mController.updateActiveBluetoothDevice(
+ new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress()));
+ // Verify that sending BT_ACTIVE_DEVICE_PRESENT when BLUETOOTH_DEVICE_1 isn't the currently
+ // tracked active device, that we ignore routing.
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Now update the active device so that it's scoDevice2 and verify that
+ // BT_ACTIVE_DEVICE_PRESENT is properly processed and that we route into the device.
+ mController.updateActiveBluetoothDevice(
+ new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress()));
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress());
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ BLUETOOTH_DEVICE_1);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
+ 0, scoDevice2);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH, scoDevice2, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
private void verifyConnectBluetoothDevice(int audioType) {
mController.initialize();
mController.setActive(true);