Merge "[Audiosharing] onDestroy() is not guaranteed to be called after stopSelf(). In this case callbacks are not unregistered timely and the notification kept being brought up again if any callback is received." into main

This commit is contained in:
Chelsea Hao
2024-06-27 11:13:13 +00:00
committed by Android (Google) Code Review
2 changed files with 430 additions and 218 deletions

View File

@@ -18,22 +18,29 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.LEAVE_BROADCAST_ACTION;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
@@ -43,14 +50,20 @@ import android.content.res.Resources;
import android.media.session.ISession;
import android.media.session.ISessionController;
import android.media.session.MediaSessionManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -72,10 +85,12 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowThreadUtils.class,
ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class,
ShadowAudioStreamsHelper.class,
@@ -83,6 +98,8 @@ import java.util.ArrayList;
public class AudioStreamMediaServiceTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String CHANNEL_ID = "bluetooth_notification_channel";
private static final String DEVICE_NAME = "name";
@Mock private Resources mResources;
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -91,17 +108,21 @@ public class AudioStreamMediaServiceTest {
@Mock private MediaSessionManager mMediaSessionManager;
@Mock private BluetoothEventManager mBluetoothEventManager;
@Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock private VolumeControlProfile mVolumeControlProfile;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mDevice;
@Mock private ISession mISession;
@Mock private ISessionController mISessionController;
@Mock private PackageManager mPackageManager;
@Mock private DisplayMetrics mDisplayMetrics;
@Mock private Context mContext;
private FakeFeatureFactory mFeatureFactory;
private AudioStreamMediaService mAudioStreamMediaService;
@Before
public void setUp() {
mFeatureFactory = FakeFeatureFactory.setupForTest();
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
ShadowBluetoothAdapter shadowBluetoothAdapter =
@@ -114,6 +135,9 @@ public class AudioStreamMediaServiceTest {
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.findDevice(any())).thenReturn(mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
when(mLocalBluetoothProfileManager.getVolumeControlProfile())
.thenReturn(mVolumeControlProfile);
@@ -168,6 +192,25 @@ public class AudioStreamMediaServiceTest {
verify(mVolumeControlProfile).registerCallback(any(), any());
}
@Test
public void onCreate_flagOn_createNewChannel() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mNotificationManager.getNotificationChannel(anyString())).thenReturn(null);
mAudioStreamMediaService.onCreate();
ArgumentCaptor<NotificationChannel> notificationChannelCapture =
ArgumentCaptor.forClass(NotificationChannel.class);
verify(mNotificationManager)
.createNotificationChannel(notificationChannelCapture.capture());
NotificationChannel newChannel = notificationChannelCapture.getValue();
assertThat(newChannel).isNotNull();
assertThat(newChannel.getId()).isEqualTo(CHANNEL_ID);
assertThat(newChannel.getName())
.isEqualTo(mContext.getString(com.android.settings.R.string.bluetooth));
assertThat(newChannel.getImportance()).isEqualTo(NotificationManager.IMPORTANCE_HIGH);
}
@Test
public void onDestroy_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -183,8 +226,15 @@ public class AudioStreamMediaServiceTest {
@Test
public void onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
intent.putParcelableArrayListExtra(DEVICES, devices);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.onDestroy();
verify(mBluetoothEventManager).unregisterCallback(any());
@@ -196,7 +246,6 @@ public class AudioStreamMediaServiceTest {
public void onStartCommand_noBroadcastId_stopSelf() {
mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNull();
verify(mAudioStreamMediaService).stopSelf();
}
@@ -207,7 +256,6 @@ public class AudioStreamMediaServiceTest {
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNull();
verify(mAudioStreamMediaService).stopSelf();
}
@@ -222,12 +270,179 @@ public class AudioStreamMediaServiceTest {
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNotNull();
verify(mAudioStreamMediaService, never()).stopSelf();
ArgumentCaptor<Notification> notificationCapture =
ArgumentCaptor.forClass(Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
var notification = notificationCapture.getValue();
assertThat(notification.getSmallIcon()).isNotNull();
assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
ArgumentCaptor<Notification> notification = ArgumentCaptor.forClass(Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notification.capture());
assertThat(notification.getValue().getSmallIcon()).isNotNull();
assertThat(notification.getValue().isStyle(Notification.MediaStyle.class)).isTrue();
verify(mAudioStreamMediaService, never()).stopSelf();
}
@Test
public void assistantCallback_onSourceLost_stopSelf() {
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull();
mAudioStreamMediaService.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 0);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void assistantCallback_onSourceRemoved_stopSelf() {
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull();
mAudioStreamMediaService.mBroadcastAssistantCallback.onSourceRemoved(
mDevice, /* sourceId= */ 0, /* reason= */ 0);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void bluetoothCallback_onBluetoothOff_stopSelf() {
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.mBluetoothCallback.onBluetoothStateChanged(
BluetoothAdapter.STATE_OFF);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void bluetoothCallback_onDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothAdapter.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void bluetoothCallback_onMemberDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mock(BluetoothDevice.class));
CachedBluetoothDevice member = mock(CachedBluetoothDevice.class);
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(Set.of(member));
when(member.getDevice()).thenReturn(mDevice);
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
intent.putParcelableArrayListExtra(DEVICES, devices);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothAdapter.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void mediaSessionCallback_onSeekTo_updateNotification() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onSeekTo(100);
verify(mNotificationManager).notify(anyInt(), any());
}
@Test
public void mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onPause();
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK),
eq(1));
}
@Test
public void mediaSessionCallback_onPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onPlay();
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK),
eq(0));
}
@Test
public void mediaSessionCallback_onCustomAction_leaveBroadcast() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onCustomAction(
LEAVE_BROADCAST_ACTION, Bundle.EMPTY);
verify(mAudioStreamsHelper).removeSource(anyInt());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
}
@Test
public void volumeControlCallback_onDeviceVolumeChanged_updateNotification() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mVolumeControlCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.mVolumeControlCallback.onDeviceVolumeChanged(
mDevice, /* volume= */ 0);
verify(mNotificationManager).notify(anyInt(), any());
}
@Test
public void onBind_returnNull() {
IBinder binder = mAudioStreamMediaService.onBind(new Intent());
assertThat(binder).isNull();
}
private Intent setupIntent() {
when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
intent.putParcelableArrayListExtra(DEVICES, devices);
return intent;
}
}