Merge changes I6322ccbb,Idc96c23d into main

* changes:
  [Audiosharing] Add tests.
  [Audiosharing] Add logging 3.
This commit is contained in:
Chelsea Hao
2024-06-14 11:07:49 +00:00
committed by Android (Google) Code Review
15 changed files with 1281 additions and 112 deletions

View File

@@ -0,0 +1,327 @@
/*
* Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment.KEY_BROADCAST_METADATA;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.androidx.fragment.FragmentController;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class,
})
public class AudioStreamConfirmDialogTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String VALID_METADATA =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
private static final String DEVICE_NAME = "device_name";
private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock private LocalBluetoothManager mLocalBluetoothManager;
@Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private VolumeControlProfile mVolumeControl;
@Mock private BluetoothDevice mBluetoothDevice;
private AudioStreamConfirmDialog mDialogFragment;
@Before
public void setUp() {
ShadowBluetoothAdapter shadowBluetoothAdapter =
Shadow.extract(BluetoothAdapter.getDefaultAdapter());
shadowBluetoothAdapter.setEnabled(true);
shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile())
.thenReturn(mAssistant);
when(mLocalBluetoothProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
when(mBroadcast.isProfileReady()).thenReturn(true);
when(mAssistant.isProfileReady()).thenReturn(true);
when(mVolumeControl.isProfileReady()).thenReturn(true);
mDialogFragment = new AudioStreamConfirmDialog();
}
@After
public void tearDown() {
ShadowBluetoothUtils.reset();
mDialogFragment.dismiss();
}
@Test
public void showDialog_unsupported() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
FragmentController.setupFragment(
mDialogFragment,
FragmentActivity.class,
/* containerViewId= */ 0,
/* bundle= */ null);
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED);
var dialog = mDialogFragment.getDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_cannot_listen));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(
mContext.getString(
R.string.audio_streams_dialog_unsupported_device_subtitle));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.GONE);
assertThat(leftButton.hasOnClickListeners()).isFalse();
View rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(rightButton.hasOnClickListeners()).isTrue();
}
@Test
public void showDialog_noLeDevice() {
FragmentController.setupFragment(
mDialogFragment,
FragmentActivity.class,
/* containerViewId= */ 0,
/* bundle= */ null);
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE);
var dialog = mDialogFragment.getDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_subtitle));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.VISIBLE);
Button rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_button));
assertThat(rightButton.hasOnClickListeners()).isTrue();
}
@Test
public void showDialog_noMetadata() {
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mBluetoothDevice);
when(mAssistant.getDevicesMatchingConnectionStates(any())).thenReturn(devices);
when(mBluetoothDevice.getAlias()).thenReturn(DEVICE_NAME);
FragmentController.setupFragment(
mDialogFragment,
FragmentActivity.class,
/* containerViewId= */ 0,
/* bundle= */ null);
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR);
var dialog = mDialogFragment.getDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_cannot_listen));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(
mContext.getString(R.string.audio_streams_dialog_cannot_play, DEVICE_NAME));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.GONE);
assertThat(leftButton.hasOnClickListeners()).isFalse();
View rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(rightButton.hasOnClickListeners()).isTrue();
}
@Test
public void showDialog_invalidMetadata() {
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mBluetoothDevice);
when(mAssistant.getDevicesMatchingConnectionStates(any())).thenReturn(devices);
when(mBluetoothDevice.getAlias()).thenReturn(DEVICE_NAME);
Intent intent = new Intent();
intent.putExtra(KEY_BROADCAST_METADATA, "invalid");
FragmentController.of(mDialogFragment, intent)
.create(/* containerViewId= */ 0, /* bundle= */ null)
.start()
.resume()
.visible()
.get();
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR);
var dialog = mDialogFragment.getDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_cannot_listen));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(
mContext.getString(R.string.audio_streams_dialog_cannot_play, DEVICE_NAME));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.GONE);
assertThat(leftButton.hasOnClickListeners()).isFalse();
View rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(rightButton.hasOnClickListeners()).isTrue();
}
@Test
public void showDialog_confirmListen() {
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mBluetoothDevice);
when(mAssistant.getDevicesMatchingConnectionStates(any())).thenReturn(devices);
when(mBluetoothDevice.getAlias()).thenReturn(DEVICE_NAME);
Intent intent = new Intent();
intent.putExtra(KEY_BROADCAST_METADATA, VALID_METADATA);
FragmentController.of(mDialogFragment, intent)
.create(/* containerViewId= */ 0, /* bundle= */ null)
.start()
.resume()
.visible()
.get();
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN);
var dialog = mDialogFragment.getDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(
mContext.getString(R.string.audio_streams_dialog_listen_to_audio_stream));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.VISIBLE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(
mContext.getString(
R.string.audio_streams_dialog_control_volume, DEVICE_NAME));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.VISIBLE);
Button rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getText())
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_listen));
assertThat(rightButton.hasOnClickListeners()).isTrue();
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2024 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.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.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
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.NotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.media.session.ISession;
import android.media.session.ISessionController;
import android.media.session.MediaSessionManager;
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.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class,
ShadowAudioStreamsHelper.class,
})
public class AudioStreamMediaServiceTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private Resources mResources;
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@Mock private AudioStreamsHelper mAudioStreamsHelper;
@Mock private NotificationManager mNotificationManager;
@Mock private MediaSessionManager mMediaSessionManager;
@Mock private BluetoothEventManager mBluetoothEventManager;
@Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
@Mock private VolumeControlProfile mVolumeControlProfile;
@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 AudioStreamMediaService mAudioStreamMediaService;
@Before
public void setUp() {
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
ShadowBluetoothAdapter shadowBluetoothAdapter =
Shadow.extract(BluetoothAdapter.getDefaultAdapter());
shadowBluetoothAdapter.setEnabled(true);
shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getVolumeControlProfile())
.thenReturn(mVolumeControlProfile);
mAudioStreamMediaService = spy(new AudioStreamMediaService());
ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
when(mAudioStreamMediaService.getSystemService(anyString()))
.thenReturn(mMediaSessionManager);
when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
try {
when(mISession.getController()).thenReturn(mISessionController);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
doReturn(mNotificationManager)
.when(mAudioStreamMediaService)
.getSystemService(NotificationManager.class);
when(mAudioStreamMediaService.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mAudioStreamMediaService.getResources()).thenReturn(mResources);
when(mAudioStreamMediaService.getPackageManager()).thenReturn(mPackageManager);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
mDisplayMetrics.density = 1.5f;
}
@After
public void tearDown() {
mAudioStreamMediaService.stopSelf();
ShadowBluetoothUtils.reset();
ShadowAudioStreamsHelper.reset();
}
@Test
public void onCreate_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
verify(mNotificationManager, never()).createNotificationChannel(any());
verify(mBluetoothEventManager, never()).registerCallback(any());
verify(mLeBroadcastAssistant, never()).registerServiceCallBack(any(), any());
verify(mVolumeControlProfile, never()).registerCallback(any(), any());
}
@Test
public void onCreate_flagOn_init() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
verify(mNotificationManager).createNotificationChannel(any());
verify(mBluetoothEventManager).registerCallback(any());
verify(mLeBroadcastAssistant).registerServiceCallBack(any(), any());
verify(mVolumeControlProfile).registerCallback(any(), any());
}
@Test
public void onDestroy_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onDestroy();
verify(mBluetoothEventManager, never()).unregisterCallback(any());
verify(mLeBroadcastAssistant, never()).unregisterServiceCallBack(any());
verify(mVolumeControlProfile, never()).unregisterCallback(any());
}
@Test
public void onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onDestroy();
verify(mBluetoothEventManager).unregisterCallback(any());
verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
verify(mVolumeControlProfile).unregisterCallback(any());
}
@Test
public void onStartCommand_noBroadcastId_stopSelf() {
mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNull();
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void onStartCommand_noDevice_stopSelf() {
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNull();
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void onStartCommand_createSessionAndStartForeground() {
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
intent.putParcelableArrayListExtra(DEVICES, devices);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mLocalSession).isNotNull();
verify(mAudioStreamMediaService, never()).stopSelf();
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();
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.flags.Flags;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class,
ShadowAudioStreamsHelper.class,
})
public class AudioStreamsCategoryControllerTest {
private static final String KEY = "audio_streams_settings_category";
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothProfileManager mBtProfileManager;
@Mock private BluetoothEventManager mBluetoothEventManager;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private VolumeControlProfile mVolumeControl;
@Mock private PreferenceScreen mScreen;
@Mock private AudioStreamsHelper mAudioStreamsHelper;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
private AudioStreamsCategoryController mController;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private LocalBluetoothManager mLocalBluetoothManager;
private Preference mPreference;
@Before
public void setUp() {
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
when(mBroadcast.isProfileReady()).thenReturn(true);
when(mAssistant.isProfileReady()).thenReturn(true);
when(mVolumeControl.isProfileReady()).thenReturn(true);
mController = new AudioStreamsCategoryController(mContext, KEY);
mPreference = new Preference(mContext);
when(mScreen.findPreference(KEY)).thenReturn(mPreference);
mController.displayPreference(mScreen);
mPreference.setVisible(false);
}
@After
public void tearDown() {
ShadowAudioStreamsHelper.reset();
ShadowBluetoothUtils.reset();
}
@Test
public void getAvailabilityStatus_flagOn() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_flagOff() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void onStart_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.onStart(mLifecycleOwner);
verify(mBluetoothEventManager, never()).registerCallback(any());
}
@Test
public void onStart_flagOn_registerCallback() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.onStart(mLifecycleOwner);
verify(mBluetoothEventManager).registerCallback(any());
}
@Test
public void onStop_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.onStop(mLifecycleOwner);
verify(mBluetoothEventManager, never()).unregisterCallback(any());
}
@Test
public void onStop_flagOn_unregisterCallback() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.onStop(mLifecycleOwner);
verify(mBluetoothEventManager).unregisterCallback(any());
}
@Test
public void updateVisibility_flagOff_invisible() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void updateVisibility_noConnectedLe_invisible() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void updateVisibility_isNotProfileReady_invisible() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
mCachedBluetoothDevice);
when(mVolumeControl.isProfileReady()).thenReturn(false);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void updateVisibility_isBroadcasting_invisible() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
mCachedBluetoothDevice);
when(mBroadcast.isEnabled(any())).thenReturn(true);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void updateVisibility_isBluetoothOff_invisible() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
mCachedBluetoothDevice);
mShadowBluetoothAdapter.setEnabled(false);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void updateVisibility_visible() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
mCachedBluetoothDevice);
mController.displayPreference(mScreen);
mController.updateVisibility();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isTrue();
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment.KEY_BROADCAST_METADATA;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AudioStreamsDashboardFragmentTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String VALID_METADATA =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
private Context mContext;
private AudioStreamsProgressCategoryController mController;
private TestFragment mTestFragment;
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mTestFragment = spy(new TestFragment());
doReturn(mContext).when(mTestFragment).getContext();
mController = spy(new AudioStreamsProgressCategoryController(mContext, "key"));
doReturn(mController).when(mTestFragment).use(AudioStreamsProgressCategoryController.class);
}
@Test
public void getPreferenceScreenResId_returnsCorrectXml() {
assertThat(mTestFragment.getPreferenceScreenResId())
.isEqualTo(R.xml.bluetooth_le_audio_streams);
}
@Test
public void getLogTag_returnsCorrectTag() {
assertThat(mTestFragment.getLogTag()).isEqualTo("AudioStreamsDashboardFrag");
}
@Test
public void getHelpResource_returnsCorrectResource() {
assertThat(mTestFragment.getHelpResource()).isEqualTo(R.string.help_url_audio_sharing);
}
@Test
public void onActivityResult_invalidRequestCode_doNothing() {
mTestFragment.onAttach(mContext);
mTestFragment.onActivityResult(0, 0, null);
verify(mController, never()).setSourceFromQrCode(any(), any());
}
@Test
public void onActivityResult_invalidRequestResult_doNothing() {
mTestFragment.onAttach(mContext);
mTestFragment.onActivityResult(REQUEST_SCAN_BT_BROADCAST_QR_CODE, 0, null);
verify(mController, never()).setSourceFromQrCode(any(), any());
}
@Test
public void onActivityResult_nullData_doNothing() {
mTestFragment.onAttach(mContext);
mTestFragment.onActivityResult(REQUEST_SCAN_BT_BROADCAST_QR_CODE, Activity.RESULT_OK, null);
verify(mController, never()).setSourceFromQrCode(any(), any());
}
@Test
public void onActivityResult_setSourceFromQrCode() {
mTestFragment.onAttach(mContext);
Intent intent = new Intent();
intent.putExtra(KEY_BROADCAST_METADATA, VALID_METADATA);
mTestFragment.onActivityResult(
REQUEST_SCAN_BT_BROADCAST_QR_CODE, Activity.RESULT_OK, intent);
verify(mController).setSourceFromQrCode(any(), any());
}
public static class TestFragment extends AudioStreamsDashboardFragment {
@Override
protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
return super.use(clazz);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.Comparator;
@RunWith(RobolectricTestRunner.class)
public class AudioStreamsProgressCategoryPreferenceTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock PreferenceManager mPreferenceManager;
private Context mContext;
private AudioStreamsProgressCategoryPreference mPreference;
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mPreference = spy(new AudioStreamsProgressCategoryPreference(mContext));
when(mPreference.getPreferenceManager()).thenReturn(mPreferenceManager);
}
@Test
public void addAudioStreamPreference_singlePreference() {
AudioStreamPreference first = new AudioStreamPreference(mContext, null);
mPreference.addAudioStreamPreference(first, (p1, p2) -> 0);
assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
assertThat(mPreference.getPreference(0)).isEqualTo(first);
}
@Test
public void addAudioStreamPreference_multiPreference_sorted() {
Comparator<AudioStreamPreference> c =
Comparator.comparingInt(AudioStreamPreference::getOrder);
AudioStreamPreference first = new AudioStreamPreference(mContext, null);
first.setOrder(1);
AudioStreamPreference second = new AudioStreamPreference(mContext, null);
second.setOrder(0);
mPreference.addAudioStreamPreference(first, c);
mPreference.addAudioStreamPreference(second, c);
assertThat(mPreference.getPreferenceCount()).isEqualTo(2);
assertThat(mPreference.getPreference(0)).isEqualTo(second);
assertThat(mPreference.getPreference(1)).isEqualTo(first);
}
@Test
public void removeAudioStreamPreferences_shouldBeEmpty() {
Comparator<AudioStreamPreference> c =
Comparator.comparingInt(AudioStreamPreference::getOrder);
AudioStreamPreference first = new AudioStreamPreference(mContext, null);
first.setOrder(0);
AudioStreamPreference second = new AudioStreamPreference(mContext, null);
second.setOrder(1);
mPreference.addAudioStreamPreference(first, c);
mPreference.addAudioStreamPreference(second, c);
mPreference.removeAudioStreamPreferences();
assertThat(mPreference.getPreferenceCount()).isEqualTo(0);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AudioStreamsRepositoryTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String METADATA_STR =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
private static final String TEST_SHARED_PREFERENCE = "AudioStreamsRepositoryTestPref";
private final BluetoothLeBroadcastMetadata mMetadata =
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(METADATA_STR);
private Context mContext;
private AudioStreamsRepository mAudioStreamsRepository;
@Before
public void setUp() {
mContext = spy(ApplicationProvider.getApplicationContext());
doReturn(getSharedPreferences()).when(mContext).getSharedPreferences(anyString(), anyInt());
mAudioStreamsRepository = AudioStreamsRepository.getInstance();
}
@Test
public void cacheAndGetMetadata_sameId() {
mAudioStreamsRepository.cacheMetadata(mMetadata);
assertThat(mMetadata).isNotNull();
assertThat(mAudioStreamsRepository.getCachedMetadata(mMetadata.getBroadcastId()))
.isEqualTo(mMetadata);
}
@Test
public void cacheAndGetMetadata_differentId() {
mAudioStreamsRepository.cacheMetadata(mMetadata);
assertThat(mMetadata).isNotNull();
assertThat(mAudioStreamsRepository.getCachedMetadata(1)).isNull();
}
@Test
public void saveAndGetMetadata_sameId() {
mAudioStreamsRepository.saveMetadata(mContext, mMetadata);
assertThat(mMetadata).isNotNull();
assertThat(mAudioStreamsRepository.getSavedMetadata(mContext, mMetadata.getBroadcastId()))
.isEqualTo(mMetadata);
}
@Test
public void saveAndGetMetadata_differentId() {
mAudioStreamsRepository.saveMetadata(mContext, mMetadata);
assertThat(mMetadata).isNotNull();
assertThat(mAudioStreamsRepository.getSavedMetadata(mContext, 1)).isNull();
}
private SharedPreferences getSharedPreferences() {
return mContext.getSharedPreferences(TEST_SHARED_PREFERENCE, Context.MODE_PRIVATE);
}
}

View File

@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.robolectric.annotation.Implementation;
@@ -63,4 +64,9 @@ public class ShadowAudioStreamsHelper {
LocalBluetoothManager manager) {
return Optional.ofNullable(sCachedBluetoothDevice);
}
@Implementation
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return sMockHelper.getLeBroadcastAssistant();
}
}