Loading flags/telecom_call_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,17 @@ flag { } } # OWNER=breadley TARGET=24Q4 flag { name: "use_stream_voice_call_tones" namespace: "telecom" description: "Use STREAM_VOICE_CALL only for ToneGenerator" bug: "363262590" metadata { purpose: PURPOSE_BUGFIX } } # OWNER=breadley TARGET=25Q2 flag { name: "enable_respond_via_sms_manager_async" Loading src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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; import com.android.server.telecom.bluetooth.BluetoothRouteManager; /** * A class that acts as a listener to things that could change call audio routing, namely * bluetooth status, wired headset status, and dock status. */ public class CallAudioRoutePeripheralAdapter implements WiredHeadsetManager.Listener, DockManager.Listener, BluetoothRouteManager.BluetoothStateListener { private final CallAudioRouteAdapter mCallAudioAdapter; private final BluetoothRouteManager mBluetoothRouteManager; private final AsyncRingtonePlayer mRingtonePlayer; public CallAudioRoutePeripheralAdapter( CallAudioRouteAdapter callAudioRouteAdapter, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, DockManager dockManager, AsyncRingtonePlayer ringtonePlayer) { mCallAudioAdapter = callAudioRouteAdapter; mBluetoothRouteManager = bluetoothManager; mRingtonePlayer = ringtonePlayer; mBluetoothRouteManager.setListener(this); wiredHeadsetManager.addListener(this); dockManager.addListener(this); } public boolean isBluetoothAudioOn() { return mBluetoothRouteManager.isBluetoothAudioConnectedOrPending(); } public boolean isHearingAidDeviceOn() { return mBluetoothRouteManager.isCachedHearingAidDevice( mBluetoothRouteManager.getBluetoothAudioConnectedDevice()); } public boolean isLeAudioDeviceOn() { return mBluetoothRouteManager.isCachedLeAudioDevice( mBluetoothRouteManager.getBluetoothAudioConnectedDevice()); } @Override public void onBluetoothDeviceListChanged() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED); } @Override public void onBluetoothActiveDevicePresent() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); } @Override public void onBluetoothActiveDeviceGone() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); } @Override public void onBluetoothAudioConnected() { mRingtonePlayer.updateBtActiveState(true); mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); } @Override public void onBluetoothAudioConnecting() { mRingtonePlayer.updateBtActiveState(false); // Pretend like audio is connected when communicating w/ CARSM. mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); } @Override public void onBluetoothAudioDisconnected() { mRingtonePlayer.updateBtActiveState(false); mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED); } @Override public void onUnexpectedBluetoothStateChange() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); } /** * Updates the audio route when the headset plugged in state changes. For example, if audio is * being routed over speakerphone and a headset is plugged in then switch to wired headset. */ @Override public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { if (!oldIsPluggedIn && newIsPluggedIn) { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); } else if (oldIsPluggedIn && !newIsPluggedIn){ mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); } } @Override public void onDockChanged(boolean isDocked) { mCallAudioAdapter.sendMessageWithSessionInfo( isDocked ? CallAudioRouteStateMachine.CONNECT_DOCK : CallAudioRouteStateMachine.DISCONNECT_DOCK ); } } src/com/android/server/telecom/CallsManager.java +11 −2 Original line number Diff line number Diff line Loading @@ -742,6 +742,13 @@ public class CallsManager extends Call.ListenerBase bluetoothStateReceiver.setCallAudioRouteAdapter(mCallAudioRouteAdapter); bluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteAdapter); CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter( mCallAudioRouteAdapter, bluetoothManager, wiredHeadsetManager, mDockManager, asyncRingtonePlayer); AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory = (resourceId, attributes) -> { MediaPlayer mediaPlayer; Loading @@ -754,8 +761,10 @@ public class CallsManager extends Call.ListenerBase } return new InCallTonePlayer.MediaPlayerAdapterImpl(mediaPlayer); }; InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(lock, toneGeneratorFactory, mediaPlayerFactory, Looper.getMainLooper()); InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory( callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory, () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0, featureFlags, Looper.getMainLooper()); SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil(); RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context, featureFlags); Loading src/com/android/server/telecom/InCallTonePlayer.java +56 −7 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.telecom.Logging.Runnable; import android.telecom.Logging.Session; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.flags.FeatureFlags; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading @@ -49,16 +50,24 @@ public class InCallTonePlayer extends Thread { */ public static class Factory { private CallAudioManager mCallAudioManager; private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; private final TelecomSystem.SyncRoot mLock; private final ToneGeneratorFactory mToneGeneratorFactory; private final MediaPlayerFactory mMediaPlayerFactory; private final AudioManagerAdapter mAudioManagerAdapter; private final FeatureFlags mFeatureFlags; private final Looper mLooper; public Factory(TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, Looper looper) { public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter, FeatureFlags flags, Looper looper) { mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; mLock = lock; mToneGeneratorFactory = toneGeneratorFactory; mMediaPlayerFactory = mediaPlayerFactory; mAudioManagerAdapter = audioManagerAdapter; mFeatureFlags = flags; mLooper = looper; } Loading @@ -67,8 +76,9 @@ public class InCallTonePlayer extends Thread { } public InCallTonePlayer createPlayer(Call call, int tone) { return new InCallTonePlayer(call, tone, mCallAudioManager, mLock, mToneGeneratorFactory, mMediaPlayerFactory, mLooper); return new InCallTonePlayer(call, tone, mCallAudioManager, mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter, mFeatureFlags, mLooper); } } Loading @@ -85,7 +95,7 @@ public class InCallTonePlayer extends Thread { } public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter { private final MediaPlayer mMediaPlayer; private MediaPlayer mMediaPlayer; /** * Create new media player adapter backed by a real mediaplayer. Loading Loading @@ -139,6 +149,10 @@ public class InCallTonePlayer extends Thread { MediaPlayerAdapter get (int resourceId, AudioAttributes attributes); } public interface AudioManagerAdapter { boolean isVolumeOverZero(); } // The possible tones that we can play. public static final int TONE_INVALID = 0; public static final int TONE_BUSY = 1; Loading @@ -160,6 +174,7 @@ public class InCallTonePlayer extends Thread { private static final int TONE_RESOURCE_ID_UNDEFINED = -1; private static final int RELATIVE_VOLUME_EMERGENCY = 100; private static final int RELATIVE_VOLUME_HIPRI = 80; private static final int RELATIVE_VOLUME_LOPRI = 30; private static final int RELATIVE_VOLUME_UNDEFINED = -1; Loading @@ -181,9 +196,10 @@ public class InCallTonePlayer extends Thread { * when we need focus and when it can be release. This should only be manipulated from the main * thread. */ private static final AtomicInteger sTonesPlaying = new AtomicInteger(0); private static AtomicInteger sTonesPlaying = new AtomicInteger(0); private final CallAudioManager mCallAudioManager; private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; private final Handler mMainThreadHandler; Loading @@ -205,6 +221,8 @@ public class InCallTonePlayer extends Thread { private final Call mCall; private final ToneGeneratorFactory mToneGenerator; private final MediaPlayerFactory mMediaPlayerFactory; private final AudioManagerAdapter mAudioManagerAdapter; private final FeatureFlags mFeatureFlags; /** * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from Loading @@ -221,17 +239,23 @@ public class InCallTonePlayer extends Thread { Call call, int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter, FeatureFlags flags, Looper looper) { mCall = call; mState = STATE_OFF; mToneId = toneId; mCallAudioManager = callAudioManager; mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; mLock = lock; mToneGenerator = toneGeneratorFactory; mMediaPlayerFactory = mediaPlayerFactor; mAudioManagerAdapter = audioManagerAdapter; mFeatureFlags = flags; mMainThreadHandler = new Handler(looper); } Loading Loading @@ -351,7 +375,7 @@ public class InCallTonePlayer extends Thread { throw new IllegalStateException("Bad toneId: " + mToneId); } int stream = AudioManager.STREAM_VOICE_CALL; int stream = getStreamType(toneType); if (toneType != ToneGenerator.TONE_UNKNOWN) { playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis); } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) { Loading @@ -363,6 +387,31 @@ public class InCallTonePlayer extends Thread { } } /** * @param toneType The ToneGenerator tone type * @return The ToneGenerator stream type */ private int getStreamType(int toneType) { if (mFeatureFlags.useStreamVoiceCallTones()) { return AudioManager.STREAM_VOICE_CALL; } int stream = AudioManager.STREAM_VOICE_CALL; if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) { stream = AudioManager.STREAM_BLUETOOTH_SCO; } if (toneType != ToneGenerator.TONE_UNKNOWN) { if (stream == AudioManager.STREAM_BLUETOOTH_SCO) { // Override audio stream for BT le device and hearing aid device if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn() || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) { stream = AudioManager.STREAM_VOICE_CALL; } } } return stream; } /** * Play a tone generated by the {@link ToneGenerator}. * @param stream The stream on which the tone will be played. Loading tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java 0 → 100644 +200 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.test.filters.SmallTest; import com.android.server.telecom.AsyncRingtonePlayer; import com.android.server.telecom.CallAudioRoutePeripheralAdapter; import com.android.server.telecom.CallAudioRouteStateMachine; import com.android.server.telecom.DockManager; import com.android.server.telecom.WiredHeadsetManager; import com.android.server.telecom.bluetooth.BluetoothRouteManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @RunWith(JUnit4.class) public class CallAudioRoutePeripheralAdapterTest extends TelecomTestCase { CallAudioRoutePeripheralAdapter mAdapter; @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine; @Mock private BluetoothRouteManager mBluetoothRouteManager; @Mock private WiredHeadsetManager mWiredHeadsetManager; @Mock private DockManager mDockManager; @Mock private AsyncRingtonePlayer mRingtonePlayer; @Override @Before public void setUp() throws Exception { super.setUp(); mAdapter = new CallAudioRoutePeripheralAdapter( mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager, mDockManager, mRingtonePlayer); } @Override @After public void tearDown() throws Exception { super.tearDown(); } @SmallTest @Test public void testIsBluetoothAudioOn() { when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); assertFalse(mAdapter.isBluetoothAudioOn()); when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true); assertTrue(mAdapter.isBluetoothAudioOn()); } @SmallTest @Test public void testIsHearingAidDeviceOn() { when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(false); assertFalse(mAdapter.isHearingAidDeviceOn()); when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(true); assertTrue(mAdapter.isHearingAidDeviceOn()); } @SmallTest @Test public void testIsLeAudioDeviceOn() { when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(false); assertFalse(mAdapter.isLeAudioDeviceOn()); when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(true); assertTrue(mAdapter.isLeAudioDeviceOn()); } @SmallTest @Test public void testOnBluetoothDeviceListChanged() { mAdapter.onBluetoothDeviceListChanged(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED); } @SmallTest @Test public void testOnBluetoothActiveDevicePresent() { mAdapter.onBluetoothActiveDevicePresent(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); } @SmallTest @Test public void testOnBluetoothActiveDeviceGone() { mAdapter.onBluetoothActiveDeviceGone(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); } @SmallTest @Test public void testOnBluetoothAudioConnected() { mAdapter.onBluetoothAudioConnected(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); verify(mRingtonePlayer).updateBtActiveState(true); } @SmallTest @Test public void testOnBluetoothAudioConnecting() { mAdapter.onBluetoothAudioConnecting(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); verify(mRingtonePlayer).updateBtActiveState(false); } @SmallTest @Test public void testOnBluetoothAudioDisconnected() { mAdapter.onBluetoothAudioDisconnected(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED); verify(mRingtonePlayer).updateBtActiveState(false); } @SmallTest @Test public void testOnUnexpectedBluetoothStateChange() { mAdapter.onUnexpectedBluetoothStateChange(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedNoChange() { mAdapter.onWiredHeadsetPluggedInChanged(false, false); mAdapter.onWiredHeadsetPluggedInChanged(true, true); verify(mCallAudioRouteStateMachine, never()).sendMessageWithSessionInfo(anyInt()); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedPlugged() { mAdapter.onWiredHeadsetPluggedInChanged(false, true); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedUnplugged() { mAdapter.onWiredHeadsetPluggedInChanged(true, false); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); } @SmallTest @Test public void testOnDockChangedConnected() { mAdapter.onDockChanged(true); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_DOCK); } @SmallTest @Test public void testOnDockChangedDisconnected() { mAdapter.onDockChanged(false); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_DOCK); } } Loading
flags/telecom_call_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,17 @@ flag { } } # OWNER=breadley TARGET=24Q4 flag { name: "use_stream_voice_call_tones" namespace: "telecom" description: "Use STREAM_VOICE_CALL only for ToneGenerator" bug: "363262590" metadata { purpose: PURPOSE_BUGFIX } } # OWNER=breadley TARGET=25Q2 flag { name: "enable_respond_via_sms_manager_async" Loading
src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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; import com.android.server.telecom.bluetooth.BluetoothRouteManager; /** * A class that acts as a listener to things that could change call audio routing, namely * bluetooth status, wired headset status, and dock status. */ public class CallAudioRoutePeripheralAdapter implements WiredHeadsetManager.Listener, DockManager.Listener, BluetoothRouteManager.BluetoothStateListener { private final CallAudioRouteAdapter mCallAudioAdapter; private final BluetoothRouteManager mBluetoothRouteManager; private final AsyncRingtonePlayer mRingtonePlayer; public CallAudioRoutePeripheralAdapter( CallAudioRouteAdapter callAudioRouteAdapter, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, DockManager dockManager, AsyncRingtonePlayer ringtonePlayer) { mCallAudioAdapter = callAudioRouteAdapter; mBluetoothRouteManager = bluetoothManager; mRingtonePlayer = ringtonePlayer; mBluetoothRouteManager.setListener(this); wiredHeadsetManager.addListener(this); dockManager.addListener(this); } public boolean isBluetoothAudioOn() { return mBluetoothRouteManager.isBluetoothAudioConnectedOrPending(); } public boolean isHearingAidDeviceOn() { return mBluetoothRouteManager.isCachedHearingAidDevice( mBluetoothRouteManager.getBluetoothAudioConnectedDevice()); } public boolean isLeAudioDeviceOn() { return mBluetoothRouteManager.isCachedLeAudioDevice( mBluetoothRouteManager.getBluetoothAudioConnectedDevice()); } @Override public void onBluetoothDeviceListChanged() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED); } @Override public void onBluetoothActiveDevicePresent() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); } @Override public void onBluetoothActiveDeviceGone() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); } @Override public void onBluetoothAudioConnected() { mRingtonePlayer.updateBtActiveState(true); mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); } @Override public void onBluetoothAudioConnecting() { mRingtonePlayer.updateBtActiveState(false); // Pretend like audio is connected when communicating w/ CARSM. mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); } @Override public void onBluetoothAudioDisconnected() { mRingtonePlayer.updateBtActiveState(false); mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED); } @Override public void onUnexpectedBluetoothStateChange() { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); } /** * Updates the audio route when the headset plugged in state changes. For example, if audio is * being routed over speakerphone and a headset is plugged in then switch to wired headset. */ @Override public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { if (!oldIsPluggedIn && newIsPluggedIn) { mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); } else if (oldIsPluggedIn && !newIsPluggedIn){ mCallAudioAdapter.sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); } } @Override public void onDockChanged(boolean isDocked) { mCallAudioAdapter.sendMessageWithSessionInfo( isDocked ? CallAudioRouteStateMachine.CONNECT_DOCK : CallAudioRouteStateMachine.DISCONNECT_DOCK ); } }
src/com/android/server/telecom/CallsManager.java +11 −2 Original line number Diff line number Diff line Loading @@ -742,6 +742,13 @@ public class CallsManager extends Call.ListenerBase bluetoothStateReceiver.setCallAudioRouteAdapter(mCallAudioRouteAdapter); bluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteAdapter); CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter( mCallAudioRouteAdapter, bluetoothManager, wiredHeadsetManager, mDockManager, asyncRingtonePlayer); AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory = (resourceId, attributes) -> { MediaPlayer mediaPlayer; Loading @@ -754,8 +761,10 @@ public class CallsManager extends Call.ListenerBase } return new InCallTonePlayer.MediaPlayerAdapterImpl(mediaPlayer); }; InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(lock, toneGeneratorFactory, mediaPlayerFactory, Looper.getMainLooper()); InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory( callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory, () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0, featureFlags, Looper.getMainLooper()); SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil(); RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context, featureFlags); Loading
src/com/android/server/telecom/InCallTonePlayer.java +56 −7 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.telecom.Logging.Runnable; import android.telecom.Logging.Session; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.flags.FeatureFlags; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading @@ -49,16 +50,24 @@ public class InCallTonePlayer extends Thread { */ public static class Factory { private CallAudioManager mCallAudioManager; private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; private final TelecomSystem.SyncRoot mLock; private final ToneGeneratorFactory mToneGeneratorFactory; private final MediaPlayerFactory mMediaPlayerFactory; private final AudioManagerAdapter mAudioManagerAdapter; private final FeatureFlags mFeatureFlags; private final Looper mLooper; public Factory(TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, Looper looper) { public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter, FeatureFlags flags, Looper looper) { mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; mLock = lock; mToneGeneratorFactory = toneGeneratorFactory; mMediaPlayerFactory = mediaPlayerFactory; mAudioManagerAdapter = audioManagerAdapter; mFeatureFlags = flags; mLooper = looper; } Loading @@ -67,8 +76,9 @@ public class InCallTonePlayer extends Thread { } public InCallTonePlayer createPlayer(Call call, int tone) { return new InCallTonePlayer(call, tone, mCallAudioManager, mLock, mToneGeneratorFactory, mMediaPlayerFactory, mLooper); return new InCallTonePlayer(call, tone, mCallAudioManager, mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter, mFeatureFlags, mLooper); } } Loading @@ -85,7 +95,7 @@ public class InCallTonePlayer extends Thread { } public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter { private final MediaPlayer mMediaPlayer; private MediaPlayer mMediaPlayer; /** * Create new media player adapter backed by a real mediaplayer. Loading Loading @@ -139,6 +149,10 @@ public class InCallTonePlayer extends Thread { MediaPlayerAdapter get (int resourceId, AudioAttributes attributes); } public interface AudioManagerAdapter { boolean isVolumeOverZero(); } // The possible tones that we can play. public static final int TONE_INVALID = 0; public static final int TONE_BUSY = 1; Loading @@ -160,6 +174,7 @@ public class InCallTonePlayer extends Thread { private static final int TONE_RESOURCE_ID_UNDEFINED = -1; private static final int RELATIVE_VOLUME_EMERGENCY = 100; private static final int RELATIVE_VOLUME_HIPRI = 80; private static final int RELATIVE_VOLUME_LOPRI = 30; private static final int RELATIVE_VOLUME_UNDEFINED = -1; Loading @@ -181,9 +196,10 @@ public class InCallTonePlayer extends Thread { * when we need focus and when it can be release. This should only be manipulated from the main * thread. */ private static final AtomicInteger sTonesPlaying = new AtomicInteger(0); private static AtomicInteger sTonesPlaying = new AtomicInteger(0); private final CallAudioManager mCallAudioManager; private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; private final Handler mMainThreadHandler; Loading @@ -205,6 +221,8 @@ public class InCallTonePlayer extends Thread { private final Call mCall; private final ToneGeneratorFactory mToneGenerator; private final MediaPlayerFactory mMediaPlayerFactory; private final AudioManagerAdapter mAudioManagerAdapter; private final FeatureFlags mFeatureFlags; /** * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from Loading @@ -221,17 +239,23 @@ public class InCallTonePlayer extends Thread { Call call, int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter, FeatureFlags flags, Looper looper) { mCall = call; mState = STATE_OFF; mToneId = toneId; mCallAudioManager = callAudioManager; mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; mLock = lock; mToneGenerator = toneGeneratorFactory; mMediaPlayerFactory = mediaPlayerFactor; mAudioManagerAdapter = audioManagerAdapter; mFeatureFlags = flags; mMainThreadHandler = new Handler(looper); } Loading Loading @@ -351,7 +375,7 @@ public class InCallTonePlayer extends Thread { throw new IllegalStateException("Bad toneId: " + mToneId); } int stream = AudioManager.STREAM_VOICE_CALL; int stream = getStreamType(toneType); if (toneType != ToneGenerator.TONE_UNKNOWN) { playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis); } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) { Loading @@ -363,6 +387,31 @@ public class InCallTonePlayer extends Thread { } } /** * @param toneType The ToneGenerator tone type * @return The ToneGenerator stream type */ private int getStreamType(int toneType) { if (mFeatureFlags.useStreamVoiceCallTones()) { return AudioManager.STREAM_VOICE_CALL; } int stream = AudioManager.STREAM_VOICE_CALL; if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) { stream = AudioManager.STREAM_BLUETOOTH_SCO; } if (toneType != ToneGenerator.TONE_UNKNOWN) { if (stream == AudioManager.STREAM_BLUETOOTH_SCO) { // Override audio stream for BT le device and hearing aid device if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn() || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) { stream = AudioManager.STREAM_VOICE_CALL; } } } return stream; } /** * Play a tone generated by the {@link ToneGenerator}. * @param stream The stream on which the tone will be played. Loading
tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java 0 → 100644 +200 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.test.filters.SmallTest; import com.android.server.telecom.AsyncRingtonePlayer; import com.android.server.telecom.CallAudioRoutePeripheralAdapter; import com.android.server.telecom.CallAudioRouteStateMachine; import com.android.server.telecom.DockManager; import com.android.server.telecom.WiredHeadsetManager; import com.android.server.telecom.bluetooth.BluetoothRouteManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @RunWith(JUnit4.class) public class CallAudioRoutePeripheralAdapterTest extends TelecomTestCase { CallAudioRoutePeripheralAdapter mAdapter; @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine; @Mock private BluetoothRouteManager mBluetoothRouteManager; @Mock private WiredHeadsetManager mWiredHeadsetManager; @Mock private DockManager mDockManager; @Mock private AsyncRingtonePlayer mRingtonePlayer; @Override @Before public void setUp() throws Exception { super.setUp(); mAdapter = new CallAudioRoutePeripheralAdapter( mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager, mDockManager, mRingtonePlayer); } @Override @After public void tearDown() throws Exception { super.tearDown(); } @SmallTest @Test public void testIsBluetoothAudioOn() { when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); assertFalse(mAdapter.isBluetoothAudioOn()); when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true); assertTrue(mAdapter.isBluetoothAudioOn()); } @SmallTest @Test public void testIsHearingAidDeviceOn() { when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(false); assertFalse(mAdapter.isHearingAidDeviceOn()); when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(true); assertTrue(mAdapter.isHearingAidDeviceOn()); } @SmallTest @Test public void testIsLeAudioDeviceOn() { when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(false); assertFalse(mAdapter.isLeAudioDeviceOn()); when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(true); assertTrue(mAdapter.isLeAudioDeviceOn()); } @SmallTest @Test public void testOnBluetoothDeviceListChanged() { mAdapter.onBluetoothDeviceListChanged(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED); } @SmallTest @Test public void testOnBluetoothActiveDevicePresent() { mAdapter.onBluetoothActiveDevicePresent(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); } @SmallTest @Test public void testOnBluetoothActiveDeviceGone() { mAdapter.onBluetoothActiveDeviceGone(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); } @SmallTest @Test public void testOnBluetoothAudioConnected() { mAdapter.onBluetoothAudioConnected(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); verify(mRingtonePlayer).updateBtActiveState(true); } @SmallTest @Test public void testOnBluetoothAudioConnecting() { mAdapter.onBluetoothAudioConnecting(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); verify(mRingtonePlayer).updateBtActiveState(false); } @SmallTest @Test public void testOnBluetoothAudioDisconnected() { mAdapter.onBluetoothAudioDisconnected(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED); verify(mRingtonePlayer).updateBtActiveState(false); } @SmallTest @Test public void testOnUnexpectedBluetoothStateChange() { mAdapter.onUnexpectedBluetoothStateChange(); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedNoChange() { mAdapter.onWiredHeadsetPluggedInChanged(false, false); mAdapter.onWiredHeadsetPluggedInChanged(true, true); verify(mCallAudioRouteStateMachine, never()).sendMessageWithSessionInfo(anyInt()); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedPlugged() { mAdapter.onWiredHeadsetPluggedInChanged(false, true); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); } @SmallTest @Test public void testOnWiredHeadsetPluggedInChangedUnplugged() { mAdapter.onWiredHeadsetPluggedInChanged(true, false); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); } @SmallTest @Test public void testOnDockChangedConnected() { mAdapter.onDockChanged(true); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.CONNECT_DOCK); } @SmallTest @Test public void testOnDockChangedDisconnected() { mAdapter.onDockChanged(false); verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo( CallAudioRouteStateMachine.DISCONNECT_DOCK); } }