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

@@ -22,6 +22,7 @@ import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
@@ -78,12 +79,10 @@ class AddSourceWaitForResponseState extends AudioStreamStateHandler {
ThreadUtils.postOnMainThread( ThreadUtils.postOnMainThread(
() -> { () -> {
if (controller.getFragment() != null) { if (controller.getFragment() != null) {
AudioStreamsDialogFragment.show( showBroadcastUnavailableNoRetryDialog(
controller.getFragment(), controller.getFragment(),
getBroadcastUnavailableNoRetryDialog(
preference.getContext(), preference.getContext(),
AudioStreamsHelper.getBroadcastName( AudioStreamsHelper.getBroadcastName(metadata));
metadata)));
} }
}); });
} }
@@ -103,13 +102,21 @@ class AddSourceWaitForResponseState extends AudioStreamStateHandler {
return AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE; return AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
} }
private AudioStreamsDialogFragment.DialogBuilder getBroadcastUnavailableNoRetryDialog( private void showBroadcastUnavailableNoRetryDialog(
Context context, String broadcastName) { Fragment fragment, Context context, String broadcastName) {
return new AudioStreamsDialogFragment.DialogBuilder(context) var broadcastUnavailableNoRetryDialog =
.setTitle(context.getString(R.string.audio_streams_dialog_stream_is_not_available)) new AudioStreamsDialogFragment.DialogBuilder(context)
.setTitle(
context.getString(
R.string.audio_streams_dialog_stream_is_not_available))
.setSubTitle1(broadcastName) .setSubTitle1(broadcastName)
.setSubTitle2(context.getString(R.string.audio_streams_is_not_playing)) .setSubTitle2(context.getString(R.string.audio_streams_is_not_playing))
.setRightButtonText(context.getString(R.string.audio_streams_dialog_close)) .setRightButtonText(context.getString(R.string.audio_streams_dialog_close))
.setRightButtonOnClickListener(AlertDialog::dismiss); .setRightButtonOnClickListener(AlertDialog::dismiss);
AudioStreamsDialogFragment.show(
fragment,
broadcastUnavailableNoRetryDialog,
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_JOIN_FAILED_TIMEOUT);
} }
} }

View File

@@ -21,101 +21,84 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.bluetooth.Utils; import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt; import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.google.common.base.Strings;
public class AudioStreamConfirmDialog extends InstrumentedDialogFragment { public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
private static final String TAG = "AudioStreamConfirmDialog"; private static final String TAG = "AudioStreamConfirmDialog";
private static final int DEFAULT_DEVICE_NAME = R.string.audio_streams_dialog_default_device; private static final int DEFAULT_DEVICE_NAME = R.string.audio_streams_dialog_default_device;
@Nullable private LocalBluetoothManager mLocalBluetoothManager; private Context mContext;
@Nullable private LocalBluetoothProfileManager mProfileManager;
@Nullable private Activity mActivity; @Nullable private Activity mActivity;
@Nullable private String mBroadcastMetadataStr;
@Nullable private BluetoothLeBroadcastMetadata mBroadcastMetadata; @Nullable private BluetoothLeBroadcastMetadata mBroadcastMetadata;
private boolean mIsRequestValid = false; @Nullable private BluetoothDevice mConnectedDevice;
private int mAudioStreamConfirmDialogId = SettingsEnums.PAGE_UNKNOWN;
@Override
public void onAttach(Context context) {
mContext = context;
mActivity = getActivity();
if (mActivity == null) {
Log.w(TAG, "onAttach() mActivity is null!");
return;
}
Intent intent = mActivity.getIntent();
mBroadcastMetadata = getMetadata(intent);
mConnectedDevice = getConnectedDevice();
mAudioStreamConfirmDialogId =
getDialogId(mBroadcastMetadata != null, mConnectedDevice != null);
super.onAttach(context);
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setShowsDialog(true); setShowsDialog(true);
mActivity = getActivity();
if (mActivity == null) {
Log.w(TAG, "onCreate() mActivity is null!");
return;
}
mLocalBluetoothManager = Utils.getLocalBluetoothManager(mActivity);
mProfileManager =
mLocalBluetoothManager == null ? null : mLocalBluetoothManager.getProfileManager();
mBroadcastMetadataStr = mActivity.getIntent().getStringExtra(KEY_BROADCAST_METADATA);
if (Strings.isNullOrEmpty(mBroadcastMetadataStr)) {
Log.w(TAG, "onCreate() mBroadcastMetadataStr is null or empty!");
return;
}
mBroadcastMetadata =
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
mBroadcastMetadataStr);
if (mBroadcastMetadata == null) {
Log.w(TAG, "onCreate() mBroadcastMetadata is null!");
} else {
mIsRequestValid = true;
}
} }
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
if (!AudioSharingUtils.isFeatureEnabled()) { return switch (mAudioStreamConfirmDialogId) {
return getUnsupporteDialog(); case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED ->
} getUnsupportedDialog();
if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) { case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE -> getNoLeDeviceDialog();
CachedBluetoothDevice connectedLeDevice = case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN -> getConfirmDialog();
AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected( default -> getErrorDialog();
mLocalBluetoothManager) };
.orElse(null);
if (connectedLeDevice == null) {
return getNoLeDeviceDialog();
}
String deviceName = connectedLeDevice.getName();
return mIsRequestValid ? getConfirmDialog(deviceName) : getErrorDialog(deviceName);
}
Log.d(TAG, "onCreateDialog() : profile not ready!");
String defaultDeviceName =
mActivity != null ? mActivity.getString(DEFAULT_DEVICE_NAME) : "";
return mIsRequestValid
? getConfirmDialog(defaultDeviceName)
: getErrorDialog(defaultDeviceName);
} }
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
// TODO(chelseahao): update metrics id return mAudioStreamConfirmDialogId;
return 0;
} }
private Dialog getConfirmDialog(String name) { private Dialog getConfirmDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity()) return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_listen_to_audio_stream)) .setTitle(getString(R.string.audio_streams_dialog_listen_to_audio_stream))
.setSubTitle1( .setSubTitle1(
mBroadcastMetadata != null mBroadcastMetadata != null
? AudioStreamsHelper.getBroadcastName(mBroadcastMetadata) ? AudioStreamsHelper.getBroadcastName(mBroadcastMetadata)
: "") : "")
.setSubTitle2(getString(R.string.audio_streams_dialog_control_volume, name)) .setSubTitle2(
getString(
R.string.audio_streams_dialog_control_volume,
getConnectedDeviceName()))
.setLeftButtonText(getString(com.android.settings.R.string.cancel)) .setLeftButtonText(getString(com.android.settings.R.string.cancel))
.setLeftButtonOnClickListener( .setLeftButtonOnClickListener(
unused -> { unused -> {
@@ -127,6 +110,10 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.setRightButtonText(getString(R.string.audio_streams_dialog_listen)) .setRightButtonText(getString(R.string.audio_streams_dialog_listen))
.setRightButtonOnClickListener( .setRightButtonOnClickListener(
unused -> { unused -> {
mMetricsFeatureProvider.action(
getActivity(),
SettingsEnums
.ACTION_AUDIO_STREAM_CONFIRM_LAUNCH_MAIN_BUTTON_CLICK);
launchAudioStreamsActivity(); launchAudioStreamsActivity();
dismiss(); dismiss();
if (mActivity != null) { if (mActivity != null) {
@@ -136,7 +123,7 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.build(); .build();
} }
private Dialog getUnsupporteDialog() { private Dialog getUnsupportedDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity()) return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_cannot_listen)) .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
.setSubTitle2(getString(R.string.audio_streams_dialog_unsupported_device_subtitle)) .setSubTitle2(getString(R.string.audio_streams_dialog_unsupported_device_subtitle))
@@ -151,10 +138,13 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.build(); .build();
} }
private Dialog getErrorDialog(String name) { private Dialog getErrorDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity()) return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_cannot_listen)) .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
.setSubTitle2(getString(R.string.audio_streams_dialog_cannot_play, name)) .setSubTitle2(
getString(
R.string.audio_streams_dialog_cannot_play,
getConnectedDeviceName()))
.setRightButtonText(getString(R.string.audio_streams_dialog_close)) .setRightButtonText(getString(R.string.audio_streams_dialog_close))
.setRightButtonOnClickListener( .setRightButtonOnClickListener(
unused -> { unused -> {
@@ -181,11 +171,12 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.setRightButtonText(getString(R.string.audio_streams_dialog_no_le_device_button)) .setRightButtonText(getString(R.string.audio_streams_dialog_no_le_device_button))
.setRightButtonOnClickListener( .setRightButtonOnClickListener(
dialog -> { dialog -> {
if (mActivity != null) { new SubSettingLauncher(mContext)
mActivity.startActivity( .setDestination(
new Intent(Settings.ACTION_BLUETOOTH_SETTINGS) ConnectedDeviceDashboardFragment.class.getName())
.setPackage(mActivity.getPackageName())); .setSourceMetricsCategory(
} SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE)
.launch();
dismiss(); dismiss();
if (mActivity != null) { if (mActivity != null) {
mActivity.finish(); mActivity.finish();
@@ -196,14 +187,60 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
private void launchAudioStreamsActivity() { private void launchAudioStreamsActivity() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(KEY_BROADCAST_METADATA, mBroadcastMetadataStr); bundle.putParcelable(KEY_BROADCAST_METADATA, mBroadcastMetadata);
if (mActivity != null) { if (mActivity != null) {
new SubSettingLauncher(getActivity()) new SubSettingLauncher(getActivity())
.setTitleText(getString(R.string.audio_streams_activity_title)) .setTitleText(getString(R.string.audio_streams_activity_title))
.setDestination(AudioStreamsDashboardFragment.class.getName()) .setDestination(AudioStreamsDashboardFragment.class.getName())
.setArguments(bundle) .setArguments(bundle)
.setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN) .setSourceMetricsCategory(getMetricsCategory())
.launch(); .launch();
} }
} }
private @Nullable BluetoothLeBroadcastMetadata getMetadata(Intent intent) {
String metadata = intent.getStringExtra(KEY_BROADCAST_METADATA);
if (metadata == null || metadata.isEmpty()) {
return null;
}
return BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(metadata);
}
private int getDialogId(boolean hasMetadata, boolean hasConnectedDevice) {
if (!AudioSharingUtils.isFeatureEnabled()) {
return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED;
}
if (!hasConnectedDevice) {
return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE;
}
return hasMetadata
? SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN
: SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR;
}
@Nullable
private BluetoothDevice getConnectedDevice() {
var localBluetoothManager = Utils.getLocalBluetoothManager(getActivity());
if (localBluetoothManager == null) {
return null;
}
LocalBluetoothLeBroadcastAssistant assistant =
localBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
return null;
}
var devices =
assistant.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED});
return devices.isEmpty() ? null : devices.get(0);
}
private String getConnectedDeviceName() {
if (mConnectedDevice != null) {
String alias = mConnectedDevice.getAlias();
return TextUtils.isEmpty(alias) ? getString(DEFAULT_DEVICE_NAME) : alias;
}
Log.w(TAG, "getConnectedDeviceName : no connected device!");
return getString(DEFAULT_DEVICE_NAME);
}
} }

View File

@@ -37,6 +37,7 @@ import android.util.Log;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.bluetooth.Utils; import com.android.settings.bluetooth.Utils;
@@ -196,7 +197,7 @@ public class AudioStreamMediaService extends Service {
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked. // override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
private int mLatestPositiveVolume = 25; private int mLatestPositiveVolume = 25;
private boolean mIsMuted = false; private boolean mIsMuted = false;
@Nullable private MediaSession mLocalSession; @VisibleForTesting @Nullable MediaSession mLocalSession;
@Override @Override
public void onCreate() { public void onCreate() {
@@ -228,7 +229,7 @@ public class AudioStreamMediaService extends Service {
NotificationChannel notificationChannel = NotificationChannel notificationChannel =
new NotificationChannel( new NotificationChannel(
CHANNEL_ID, CHANNEL_ID,
this.getString(com.android.settings.R.string.bluetooth), getString(com.android.settings.R.string.bluetooth),
NotificationManager.IMPORTANCE_HIGH); NotificationManager.IMPORTANCE_HIGH);
mNotificationManager.createNotificationChannel(notificationChannel); mNotificationManager.createNotificationChannel(notificationChannel);
} }
@@ -403,7 +404,7 @@ public class AudioStreamMediaService extends Service {
new Notification.Builder(this, CHANNEL_ID) new Notification.Builder(this, CHANNEL_ID)
.setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing) .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
.setStyle(mediaStyle) .setStyle(mediaStyle)
.setContentText(this.getString(BROADCAST_CONTENT_TEXT)) .setContentText(getString(BROADCAST_CONTENT_TEXT))
.setSilent(true); .setSilent(true);
return notificationBuilder.build(); return notificationBuilder.build();
} }

View File

@@ -60,6 +60,7 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo
@Override @Override
public void onStart(@NonNull LifecycleOwner owner) { public void onStart(@NonNull LifecycleOwner owner) {
if (!isAvailable()) return;
super.onStart(owner); super.onStart(owner);
if (mLocalBtManager != null) { if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback); mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
@@ -68,6 +69,7 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo
@Override @Override
public void onStop(@NonNull LifecycleOwner owner) { public void onStop(@NonNull LifecycleOwner owner) {
if (!isAvailable()) return;
super.onStop(owner); super.onStop(owner);
if (mLocalBtManager != null) { if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback); mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);

View File

@@ -18,6 +18,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@@ -43,15 +44,16 @@ import java.util.function.Consumer;
public class AudioStreamsDialogFragment extends InstrumentedDialogFragment { public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioStreamsDialogFragment"; private static final String TAG = "AudioStreamsDialogFragment";
private final DialogBuilder mDialogBuilder; private final DialogBuilder mDialogBuilder;
private int mDialogId = SettingsEnums.PAGE_UNKNOWN;
AudioStreamsDialogFragment(DialogBuilder dialogBuilder) { AudioStreamsDialogFragment(DialogBuilder dialogBuilder, int dialogId) {
mDialogBuilder = dialogBuilder; mDialogBuilder = dialogBuilder;
mDialogId = dialogId;
} }
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
// TODO(chelseahao): update metrics id return mDialogId;
return 0;
} }
@Override @Override
@@ -64,14 +66,15 @@ public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
* *
* @param host The fragment to host the dialog. * @param host The fragment to host the dialog.
* @param dialogBuilder The builder for constructing the dialog. * @param dialogBuilder The builder for constructing the dialog.
* @param dialogId The dialog settings enum for logging
*/ */
public static void show(Fragment host, DialogBuilder dialogBuilder) { public static void show(Fragment host, DialogBuilder dialogBuilder, int dialogId) {
if (!host.isAdded()) { if (!host.isAdded()) {
Log.w(TAG, "The host fragment is not added to the activity!"); Log.w(TAG, "The host fragment is not added to the activity!");
return; return;
} }
FragmentManager manager = host.getChildFragmentManager(); FragmentManager manager = host.getChildFragmentManager();
(new AudioStreamsDialogFragment(dialogBuilder)).show(manager, TAG); (new AudioStreamsDialogFragment(dialogBuilder, dialogId)).show(manager, TAG);
} }
static void dismissAll(Fragment host) { static void dismissAll(Fragment host) {

View File

@@ -139,8 +139,10 @@ public class AudioStreamsHelper {
.toList(); .toList();
} }
/** Retrieves LocalBluetoothLeBroadcastAssistant. */
@VisibleForTesting
@Nullable @Nullable
LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() { public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return mLeBroadcastAssistant; return mLeBroadcastAssistant;
} }

View File

@@ -481,7 +481,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mContext, mContext,
() -> { () -> {
if (mFragment != null) { if (mFragment != null) {
AudioStreamsDialogFragment.show(mFragment, getNoLeDeviceDialog()); AudioStreamsDialogFragment.show(
mFragment,
getNoLeDeviceDialog(),
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_NO_LE_DEVICE);
} }
}); });
} }

View File

@@ -68,13 +68,10 @@ class WaitForSyncState extends AudioStreamStateHandler {
ThreadUtils.postOnMainThread( ThreadUtils.postOnMainThread(
() -> { () -> {
if (controller.getFragment() != null) { if (controller.getFragment() != null) {
AudioStreamsDialogFragment.show( showBroadcastUnavailableDialog(
controller.getFragment(), controller.getFragment(),
getBroadcastUnavailableDialog(
preference.getContext(), preference.getContext(),
AudioStreamsHelper.getBroadcastName( AudioStreamsHelper.getBroadcastName(metadata));
metadata),
controller));
} }
}); });
} }
@@ -93,12 +90,13 @@ class WaitForSyncState extends AudioStreamStateHandler {
return AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC; return AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
} }
private AudioStreamsDialogFragment.DialogBuilder getBroadcastUnavailableDialog( private void showBroadcastUnavailableDialog(
Context context, Fragment fragment, Context context, String broadcastName) {
String broadcastName, var broadcastUnavailableDialog =
AudioStreamsProgressCategoryController controller) { new AudioStreamsDialogFragment.DialogBuilder(context)
return new AudioStreamsDialogFragment.DialogBuilder(context) .setTitle(
.setTitle(context.getString(R.string.audio_streams_dialog_stream_is_not_available)) context.getString(
R.string.audio_streams_dialog_stream_is_not_available))
.setSubTitle1(broadcastName) .setSubTitle1(broadcastName)
.setSubTitle2(context.getString(R.string.audio_streams_is_not_playing)) .setSubTitle2(context.getString(R.string.audio_streams_is_not_playing))
.setLeftButtonText(context.getString(R.string.audio_streams_dialog_close)) .setLeftButtonText(context.getString(R.string.audio_streams_dialog_close))
@@ -106,11 +104,14 @@ class WaitForSyncState extends AudioStreamStateHandler {
.setRightButtonText(context.getString(R.string.audio_streams_dialog_retry)) .setRightButtonText(context.getString(R.string.audio_streams_dialog_retry))
.setRightButtonOnClickListener( .setRightButtonOnClickListener(
dialog -> { dialog -> {
if (controller.getFragment() != null) { launchQrCodeScanFragment(context, fragment);
launchQrCodeScanFragment(context, controller.getFragment());
dialog.dismiss(); dialog.dismiss();
}
}); });
AudioStreamsDialogFragment.show(
fragment,
broadcastUnavailableDialog,
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT);
} }
private void launchQrCodeScanFragment(Context context, Fragment fragment) { private void launchQrCodeScanFragment(Context context, Fragment fragment) {

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