Merge changes I9036e560,Ieb735d39,Ia78b1fe1,I91d9a45a into main
* changes: [Audiosharing] Created test for the name and password preferences. [Audiosharing] Created test for the main controller. [Audiosharing] Listen to `onProfileConnectionStateChanged` of LE_AUDIO_BROADCAST_ASSISTANT to be more precise on device connection status upon bluetooth on/off. Also increase test coverage. [Audiosharing] Increase test coverage for audio stream states.
This commit is contained in:
@@ -95,14 +95,14 @@ public class AudioSharingNamePreference extends ValidatedEditTextPreference {
|
||||
}
|
||||
|
||||
private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
|
||||
divider.setVisibility(View.INVISIBLE);
|
||||
shareButton.setVisibility(View.INVISIBLE);
|
||||
divider.setVisibility(View.GONE);
|
||||
shareButton.setVisibility(View.GONE);
|
||||
shareButton.setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void launchAudioSharingQrCodeFragment() {
|
||||
new SubSettingLauncher(getContext())
|
||||
.setTitleText(getContext().getString(R.string.audio_streams_qr_code_page_title))
|
||||
.setTitleRes(R.string.audio_streams_qr_code_page_title)
|
||||
.setDestination(AudioStreamsQrCodeFragment.class.getName())
|
||||
.setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
|
||||
.launch();
|
||||
|
@@ -26,6 +26,7 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.Preference;
|
||||
@@ -56,7 +57,8 @@ public class AudioSharingNamePreferenceController extends BasePreferenceControll
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final String PREF_KEY = "audio_sharing_stream_name";
|
||||
|
||||
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
|
||||
@VisibleForTesting
|
||||
final BluetoothLeBroadcast.Callback mBroadcastCallback =
|
||||
new BluetoothLeBroadcast.Callback() {
|
||||
@Override
|
||||
public void onBroadcastMetadataChanged(
|
||||
|
@@ -19,12 +19,19 @@ package com.android.settings.connecteddevice.audiosharing;
|
||||
import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
@@ -41,15 +48,19 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class AudioSharingPasswordPreferenceController extends BasePreferenceController
|
||||
implements ValidatedEditTextPreference.Validator,
|
||||
AudioSharingPasswordPreference.OnDialogEventListener {
|
||||
|
||||
AudioSharingPasswordPreference.OnDialogEventListener,
|
||||
DefaultLifecycleObserver {
|
||||
private static final String TAG = "AudioSharingPasswordPreferenceController";
|
||||
private static final String PREF_KEY = "audio_sharing_stream_password";
|
||||
private static final String SHARED_PREF_NAME = "audio_sharing_settings";
|
||||
private static final String SHARED_PREF_KEY = "default_password";
|
||||
@Nullable private final ContentResolver mContentResolver;
|
||||
@Nullable private final SharedPreferences mSharedPref;
|
||||
@Nullable private final LocalBluetoothManager mBtManager;
|
||||
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
|
||||
@Nullable private AudioSharingPasswordPreference mPreference;
|
||||
private final ContentObserver mSettingsObserver;
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener mSharedPrefChangeListener;
|
||||
private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
|
||||
@@ -61,9 +72,44 @@ public class AudioSharingPasswordPreferenceController extends BasePreferenceCont
|
||||
? mBtManager.getProfileManager().getLeAudioBroadcastProfile()
|
||||
: null;
|
||||
mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
|
||||
mContentResolver = context.getContentResolver();
|
||||
mSettingsObserver = new PasswordSettingsObserver();
|
||||
mSharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
|
||||
mSharedPrefChangeListener = new PasswordSharedPrefChangeListener();
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
if (!isAvailable()) {
|
||||
Log.d(TAG, "Feature is not available.");
|
||||
return;
|
||||
}
|
||||
if (mContentResolver != null) {
|
||||
mContentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
|
||||
false,
|
||||
mSettingsObserver);
|
||||
}
|
||||
if (mSharedPref != null) {
|
||||
mSharedPref.registerOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
if (!isAvailable()) {
|
||||
Log.d(TAG, "Feature is not available.");
|
||||
return;
|
||||
}
|
||||
if (mContentResolver != null) {
|
||||
mContentResolver.unregisterContentObserver(mSettingsObserver);
|
||||
}
|
||||
if (mSharedPref != null) {
|
||||
mSharedPref.unregisterOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
@@ -125,7 +171,6 @@ public class AudioSharingPasswordPreferenceController extends BasePreferenceCont
|
||||
persistDefaultPassword(mContext, password);
|
||||
mBroadcast.setBroadcastCode(
|
||||
isPublicBroadcast ? new byte[0] : password.getBytes());
|
||||
updatePreference();
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED,
|
||||
@@ -164,32 +209,52 @@ public class AudioSharingPasswordPreferenceController extends BasePreferenceCont
|
||||
});
|
||||
}
|
||||
|
||||
private static void persistDefaultPassword(Context context, String defaultPassword) {
|
||||
private class PasswordSettingsObserver extends ContentObserver {
|
||||
PasswordSettingsObserver() {
|
||||
super(new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(TAG, "onChange, broadcast password has been changed");
|
||||
updatePreference();
|
||||
}
|
||||
}
|
||||
|
||||
private class PasswordSharedPrefChangeListener
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(
|
||||
SharedPreferences sharedPreferences, @Nullable String key) {
|
||||
if (!SHARED_PREF_KEY.equals(key)) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "onSharedPreferenceChanged, default password has been changed");
|
||||
updatePreference();
|
||||
}
|
||||
}
|
||||
|
||||
private void persistDefaultPassword(Context context, String defaultPassword) {
|
||||
if (getDefaultPassword(context).equals(defaultPassword)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences sharedPref =
|
||||
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
|
||||
if (sharedPref == null) {
|
||||
if (mSharedPref == null) {
|
||||
Log.w(TAG, "persistDefaultPassword(): sharedPref is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
SharedPreferences.Editor editor = mSharedPref.edit();
|
||||
editor.putString(SHARED_PREF_KEY, defaultPassword);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private static String getDefaultPassword(Context context) {
|
||||
SharedPreferences sharedPref =
|
||||
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
|
||||
if (sharedPref == null) {
|
||||
private String getDefaultPassword(Context context) {
|
||||
if (mSharedPref == null) {
|
||||
Log.w(TAG, "getDefaultPassword(): sharedPref is empty!");
|
||||
return "";
|
||||
}
|
||||
|
||||
String value = sharedPref.getString(SHARED_PREF_KEY, "");
|
||||
String value = mSharedPref.getString(SHARED_PREF_KEY, "");
|
||||
if (value != null && value.isEmpty()) {
|
||||
Log.w(TAG, "getDefaultPassword(): default password is empty!");
|
||||
}
|
||||
|
@@ -36,7 +36,8 @@ class AddSourceWaitForResponseState extends AudioStreamStateHandler {
|
||||
|
||||
@Nullable private static AddSourceWaitForResponseState sInstance = null;
|
||||
|
||||
private AddSourceWaitForResponseState() {}
|
||||
@VisibleForTesting
|
||||
AddSourceWaitForResponseState() {}
|
||||
|
||||
static AddSourceWaitForResponseState getInstance() {
|
||||
if (sInstance == null) {
|
||||
|
@@ -35,10 +35,10 @@ class AudioStreamStateHandler {
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
@VisibleForTesting static final int EMPTY_STRING_RES = 0;
|
||||
|
||||
final AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance();
|
||||
final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
final MetricsFeatureProvider mMetricsFeatureProvider =
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance();
|
||||
|
||||
AudioStreamStateHandler() {}
|
||||
|
||||
@@ -112,4 +112,9 @@ class AudioStreamStateHandler {
|
||||
AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() {
|
||||
return AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setAudioStreamsRepositoryForTesting(AudioStreamsRepository repository) {
|
||||
mAudioStreamsRepository = repository;
|
||||
}
|
||||
}
|
||||
|
@@ -16,24 +16,22 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settingslib.bluetooth.BluetoothCallback;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback {
|
||||
private static final String TAG = "AudioStreamsActiveDeviceSummaryUpdater";
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private final LocalBluetoothManager mBluetoothManager;
|
||||
private Context mContext;
|
||||
@Nullable private String mSummary;
|
||||
@@ -47,17 +45,20 @@ public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveDeviceChanged(
|
||||
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"onActiveDeviceChanged() with activeDevice : "
|
||||
+ (activeDevice == null ? "null" : activeDevice.getAddress())
|
||||
+ " on profile : "
|
||||
+ bluetoothProfile);
|
||||
public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
|
||||
if (bluetoothState == BluetoothAdapter.STATE_OFF) {
|
||||
notifyChangeIfNeeded();
|
||||
}
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProfileConnectionStateChanged(
|
||||
@NonNull CachedBluetoothDevice cachedDevice,
|
||||
@ConnectionState int state,
|
||||
int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||
&& (state == BluetoothAdapter.STATE_CONNECTED
|
||||
|| state == BluetoothAdapter.STATE_DISCONNECTED)) {
|
||||
notifyChangeIfNeeded();
|
||||
}
|
||||
}
|
||||
|
@@ -16,12 +16,12 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
@@ -44,9 +44,13 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo
|
||||
private final BluetoothCallback mBluetoothCallback =
|
||||
new BluetoothCallback() {
|
||||
@Override
|
||||
public void onActiveDeviceChanged(
|
||||
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
|
||||
public void onProfileConnectionStateChanged(
|
||||
@NonNull CachedBluetoothDevice cachedDevice,
|
||||
@ConnectionState int state,
|
||||
int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||
&& (state == BluetoothAdapter.STATE_CONNECTED
|
||||
|| state == BluetoothAdapter.STATE_DISCONNECTED)) {
|
||||
updateVisibility();
|
||||
}
|
||||
}
|
||||
|
@@ -71,7 +71,8 @@ public class AudioStreamsHelper {
|
||||
*
|
||||
* @param source The LE broadcast metadata representing the audio source.
|
||||
*/
|
||||
void addSource(BluetoothLeBroadcastMetadata source) {
|
||||
@VisibleForTesting
|
||||
public void addSource(BluetoothLeBroadcastMetadata source) {
|
||||
if (mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG, "addSource(): LeBroadcastAssistant is null!");
|
||||
return;
|
||||
@@ -97,7 +98,8 @@ public class AudioStreamsHelper {
|
||||
}
|
||||
|
||||
/** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
|
||||
void removeSource(int broadcastId) {
|
||||
@VisibleForTesting
|
||||
public void removeSource(int broadcastId) {
|
||||
if (mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
|
||||
return;
|
||||
|
@@ -19,7 +19,6 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.util.Log;
|
||||
|
||||
public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
|
||||
private static final String TAG = "AudioStreamsProgressCategoryCallback";
|
||||
@@ -53,10 +52,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
|
||||
@Override
|
||||
public void onSearchStarted(int reason) {
|
||||
super.onSearchStarted(reason);
|
||||
if (mCategoryController == null) {
|
||||
Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
|
||||
return;
|
||||
}
|
||||
mCategoryController.setScanning(true);
|
||||
}
|
||||
|
||||
@@ -69,10 +64,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
|
||||
@Override
|
||||
public void onSearchStopped(int reason) {
|
||||
super.onSearchStopped(reason);
|
||||
if (mCategoryController == null) {
|
||||
Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
|
||||
return;
|
||||
}
|
||||
mCategoryController.setScanning(false);
|
||||
}
|
||||
|
||||
@@ -86,10 +77,6 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
|
||||
@Override
|
||||
public void onSourceFound(BluetoothLeBroadcastMetadata source) {
|
||||
super.onSourceFound(source);
|
||||
if (mCategoryController == null) {
|
||||
Log.w(TAG, "onSourceFound() : mCategoryController is null!");
|
||||
return;
|
||||
}
|
||||
mCategoryController.handleSourceFound(source);
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import static java.util.Collections.emptyList;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
@@ -27,10 +28,12 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
|
||||
@@ -55,13 +58,35 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
implements DefaultLifecycleObserver {
|
||||
private static final String TAG = "AudioStreamsProgressCategoryController";
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final int UNSET_BROADCAST_ID = -1;
|
||||
private final BluetoothCallback mBluetoothCallback =
|
||||
@VisibleForTesting static final int UNSET_BROADCAST_ID = -1;
|
||||
|
||||
@VisibleForTesting
|
||||
final BluetoothCallback mBluetoothCallback =
|
||||
new BluetoothCallback() {
|
||||
@Override
|
||||
public void onActiveDeviceChanged(
|
||||
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
|
||||
public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
|
||||
Log.d(TAG, "onBluetoothStateChanged() with bluetoothState : " + bluetoothState);
|
||||
if (bluetoothState == BluetoothAdapter.STATE_OFF) {
|
||||
mExecutor.execute(() -> init());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProfileConnectionStateChanged(
|
||||
@NonNull CachedBluetoothDevice cachedDevice,
|
||||
@ConnectionState int state,
|
||||
int bluetoothProfile) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"onProfileConnectionStateChanged() with cachedDevice : "
|
||||
+ cachedDevice.getAddress()
|
||||
+ " with state : "
|
||||
+ state
|
||||
+ " on profile : "
|
||||
+ bluetoothProfile);
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||
&& (state == BluetoothAdapter.STATE_CONNECTED
|
||||
|| state == BluetoothAdapter.STATE_DISCONNECTED)) {
|
||||
mExecutor.execute(() -> init());
|
||||
}
|
||||
}
|
||||
@@ -92,7 +117,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
SOURCE_ADDED,
|
||||
}
|
||||
|
||||
private final Executor mExecutor;
|
||||
@VisibleForTesting Executor mExecutor;
|
||||
private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
|
||||
private final AudioStreamsHelper mAudioStreamsHelper;
|
||||
private final MediaControlHelper mMediaControlHelper;
|
||||
@@ -103,7 +128,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
|
||||
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
|
||||
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
|
||||
@Nullable private AudioStreamsDashboardFragment mFragment;
|
||||
@Nullable private Fragment mFragment;
|
||||
|
||||
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
@@ -142,12 +167,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
mExecutor.execute(this::stopScanning);
|
||||
}
|
||||
|
||||
void setFragment(AudioStreamsDashboardFragment fragment) {
|
||||
void setFragment(Fragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AudioStreamsDashboardFragment getFragment() {
|
||||
Fragment getFragment() {
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
@@ -546,7 +571,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
return preference;
|
||||
}
|
||||
|
||||
private void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
||||
@VisibleForTesting
|
||||
void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
||||
AudioStreamStateHandler stateHandler =
|
||||
switch (state) {
|
||||
case SYNCED -> SyncedState.getInstance();
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -47,9 +48,13 @@ public class AudioStreamsScanQrCodeController extends BasePreferenceController
|
||||
final BluetoothCallback mBluetoothCallback =
|
||||
new BluetoothCallback() {
|
||||
@Override
|
||||
public void onActiveDeviceChanged(
|
||||
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
|
||||
public void onProfileConnectionStateChanged(
|
||||
@NonNull CachedBluetoothDevice cachedDevice,
|
||||
@ConnectionState int state,
|
||||
int bluetoothProfile) {
|
||||
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||
&& (state == BluetoothAdapter.STATE_CONNECTED
|
||||
|| state == BluetoothAdapter.STATE_DISCONNECTED)) {
|
||||
updateVisibility();
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
|
||||
class SourceAddedState extends AudioStreamStateHandler {
|
||||
@VisibleForTesting
|
||||
@@ -32,7 +33,8 @@ class SourceAddedState extends AudioStreamStateHandler {
|
||||
|
||||
@Nullable private static SourceAddedState sInstance = null;
|
||||
|
||||
private SourceAddedState() {}
|
||||
@VisibleForTesting
|
||||
SourceAddedState() {}
|
||||
|
||||
static SourceAddedState getInstance() {
|
||||
if (sInstance == null) {
|
||||
@@ -80,13 +82,13 @@ class SourceAddedState extends AudioStreamStateHandler {
|
||||
AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
|
||||
|
||||
new SubSettingLauncher(p.getContext())
|
||||
.setTitleText(
|
||||
p.getContext().getString(R.string.audio_streams_detail_page_title))
|
||||
.setTitleRes(R.string.audio_streams_detail_page_title)
|
||||
.setDestination(AudioStreamDetailsFragment.class.getName())
|
||||
.setSourceMetricsCategory(
|
||||
controller.getFragment() == null
|
||||
!(controller.getFragment() instanceof DashboardFragment)
|
||||
? SettingsEnums.PAGE_UNKNOWN
|
||||
: controller.getFragment().getMetricsCategory())
|
||||
: ((DashboardFragment) controller.getFragment())
|
||||
.getMetricsCategory())
|
||||
.setArguments(broadcast)
|
||||
.launch();
|
||||
return true;
|
||||
|
@@ -39,7 +39,8 @@ class WaitForSyncState extends AudioStreamStateHandler {
|
||||
|
||||
@Nullable private static WaitForSyncState sInstance = null;
|
||||
|
||||
private WaitForSyncState() {}
|
||||
@VisibleForTesting
|
||||
WaitForSyncState() {}
|
||||
|
||||
static WaitForSyncState getInstance() {
|
||||
if (sInstance == null) {
|
||||
@@ -114,7 +115,8 @@ class WaitForSyncState extends AudioStreamStateHandler {
|
||||
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT);
|
||||
}
|
||||
|
||||
private void launchQrCodeScanFragment(Context context, Fragment fragment) {
|
||||
@VisibleForTesting
|
||||
void launchQrCodeScanFragment(Context context, Fragment fragment) {
|
||||
new SubSettingLauncher(context)
|
||||
.setTitleRes(R.string.audio_streams_main_page_scan_qr_code_title)
|
||||
.setDestination(AudioStreamsQrCodeScanFragment.class.getName())
|
||||
|
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothLeBroadcast;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothStatusCodes;
|
||||
import android.content.Context;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
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.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.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
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.ShadowLooper;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowBluetoothAdapter.class,
|
||||
ShadowBluetoothUtils.class,
|
||||
})
|
||||
public class AudioSharingNamePreferenceControllerTest {
|
||||
private static final String PREF_KEY = "audio_sharing_stream_name";
|
||||
private static final String BROADCAST_NAME = "broadcast_name";
|
||||
private static final CharSequence UPDATED_NAME = "updated_name";
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
@Spy Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private LocalBluetoothLeBroadcast mBroadcast;
|
||||
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
|
||||
@Mock private VolumeControlProfile mVolumeControl;
|
||||
@Mock private LocalBluetoothManager mLocalBtManager;
|
||||
@Mock private BluetoothEventManager mEventManager;
|
||||
@Mock private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock private PreferenceScreen mScreen;
|
||||
private AudioSharingNamePreferenceController mController;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
private Lifecycle mLifecycle;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private AudioSharingNamePreference mPreference;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mShadowBluetoothAdapter.setEnabled(true);
|
||||
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
|
||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
|
||||
mLocalBtManager = Utils.getLocalBtManager(mContext);
|
||||
when(mLocalBtManager.getEventManager()).thenReturn(mEventManager);
|
||||
when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager);
|
||||
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
|
||||
when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
|
||||
when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
|
||||
when(mBroadcast.isProfileReady()).thenReturn(true);
|
||||
when(mAssistant.isProfileReady()).thenReturn(true);
|
||||
when(mVolumeControl.isProfileReady()).thenReturn(true);
|
||||
when(mBroadcast.isProfileReady()).thenReturn(true);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mLifecycleOwner = () -> mLifecycle;
|
||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||
mController = new AudioSharingNamePreferenceController(mContext, PREF_KEY);
|
||||
mPreference = spy(new AudioSharingNamePreference(mContext));
|
||||
when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus_flagOn_available() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus_flagOff_unsupported() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_flagOff_doNothing() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mBroadcast, never())
|
||||
.registerServiceCallBack(
|
||||
any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_flagOn_registerCallbacks() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mBroadcast)
|
||||
.registerServiceCallBack(
|
||||
any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_flagOn_serviceNotReady_registerCallbacks() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mBroadcast.isProfileReady()).thenReturn(false);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mProfileManager)
|
||||
.addServiceListener(any(LocalBluetoothProfileManager.ServiceListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_removeCallbacks() {
|
||||
mController.onServiceConnected();
|
||||
verify(mProfileManager)
|
||||
.removeServiceListener(any(LocalBluetoothProfileManager.ServiceListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_flagOff_doNothing() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
mController.onStop(mLifecycleOwner);
|
||||
verify(mBroadcast, never())
|
||||
.unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_flagOn_unregisterCallbacks() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
mController.onStop(mLifecycleOwner);
|
||||
verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_updateName_showIcon() {
|
||||
when(mBroadcast.getBroadcastName()).thenReturn(BROADCAST_NAME);
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(true);
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreference.getText()).isEqualTo(BROADCAST_NAME);
|
||||
assertThat(mPreference.getSummary()).isEqualTo(BROADCAST_NAME);
|
||||
verify(mPreference).setValidator(any());
|
||||
verify(mPreference).setShowQrCodeIcon(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_updateName_hideIcon() {
|
||||
when(mBroadcast.getBroadcastName()).thenReturn(BROADCAST_NAME);
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(false);
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreference.getText()).isEqualTo(BROADCAST_NAME);
|
||||
assertThat(mPreference.getSummary()).isEqualTo(BROADCAST_NAME);
|
||||
verify(mPreference).setValidator(any());
|
||||
verify(mPreference).setShowQrCodeIcon(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceChange_noChange_doNothing() {
|
||||
when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
|
||||
mController.displayPreference(mScreen);
|
||||
boolean changed = mController.onPreferenceChange(mPreference, BROADCAST_NAME);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast, never()).setBroadcastName(anyString());
|
||||
verify(mBroadcast, never()).setProgramInfo(anyString());
|
||||
verify(mBroadcast, never()).updateBroadcast();
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
|
||||
|
||||
assertThat(changed).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceChange_changed_updateName_broadcasting() {
|
||||
when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(true);
|
||||
mController.displayPreference(mScreen);
|
||||
boolean changed = mController.onPreferenceChange(mPreference, UPDATED_NAME);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast).setBroadcastName(UPDATED_NAME.toString());
|
||||
verify(mBroadcast).setProgramInfo(UPDATED_NAME.toString());
|
||||
verify(mBroadcast).updateBroadcast();
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_NAME_UPDATED, 1);
|
||||
assertThat(changed).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceChange_changed_updateName_notBroadcasting() {
|
||||
when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(false);
|
||||
mController.displayPreference(mScreen);
|
||||
boolean changed = mController.onPreferenceChange(mPreference, UPDATED_NAME);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast).setBroadcastName(UPDATED_NAME.toString());
|
||||
verify(mBroadcast).setProgramInfo(UPDATED_NAME.toString());
|
||||
verify(mBroadcast, never()).updateBroadcast();
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_NAME_UPDATED, 0);
|
||||
assertThat(changed).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedCallbacks_doNotUpdateIcon() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 0);
|
||||
mController.mBroadcastCallback.onBroadcastStarted(/* reason= */ 0, /* broadcastId= */ 0);
|
||||
mController.mBroadcastCallback.onBroadcastStopFailed(/* reason= */ 0);
|
||||
mController.mBroadcastCallback.onBroadcastUpdateFailed(
|
||||
/* reason= */ 0, /* broadcastId= */ 0);
|
||||
mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 0, /* broadcastId= */ 0);
|
||||
mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 0, /* broadcastId= */ 0);
|
||||
mController.mBroadcastCallback.onPlaybackStopped(/* reason= */ 0, /* broadcastId= */ 0);
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
// Should be called once in displayPreference, but not called after callbacks
|
||||
verify(mPreference).setShowQrCodeIcon(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void broadcastOnCallback_updateIcon() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.mBroadcastCallback.onBroadcastMetadataChanged(
|
||||
/* broadcastId= */ 0, mock(BluetoothLeBroadcastMetadata.class));
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
// Should be called twice, in displayPreference and also after callback
|
||||
verify(mPreference, times(2)).setShowQrCodeIcon(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void broadcastStopCallback_updateIcon() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.mBroadcastCallback.onBroadcastStopped(/* reason= */ 0, /* broadcastId= */ 0);
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
// Should be called twice, in displayPreference and also after callback
|
||||
verify(mPreference, times(2)).setShowQrCodeIcon(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idTextValid_emptyString() {
|
||||
boolean valid = mController.isTextValid("");
|
||||
|
||||
assertThat(valid).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idTextValid_validName() {
|
||||
boolean valid = mController.isTextValid("valid name");
|
||||
|
||||
assertThat(valid).isTrue();
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioSharingNamePreferenceTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private Context mContext;
|
||||
private AudioSharingNamePreference mPreference;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mPreference = spy(new AudioSharingNamePreference(mContext, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_correctLayout() {
|
||||
assertThat(mPreference.getLayoutResource())
|
||||
.isEqualTo(
|
||||
com.android.settingslib.widget.preference.twotarget.R.layout
|
||||
.preference_two_target);
|
||||
assertThat(mPreference.getWidgetLayoutResource())
|
||||
.isEqualTo(R.layout.preference_widget_qrcode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_correctLayout_noQrCodeButton() {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
View view = inflater.inflate(mPreference.getLayoutResource(), null);
|
||||
LinearLayout widgetView = view.findViewById(android.R.id.widget_frame);
|
||||
assertThat(widgetView).isNotNull();
|
||||
inflater.inflate(mPreference.getWidgetLayoutResource(), widgetView, true);
|
||||
|
||||
var holder = PreferenceViewHolder.createInstanceForTests(view);
|
||||
mPreference.setShowQrCodeIcon(false);
|
||||
mPreference.onBindViewHolder(holder);
|
||||
|
||||
ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
|
||||
View divider =
|
||||
holder.findViewById(
|
||||
com.android.settingslib.widget.preference.twotarget.R.id
|
||||
.two_target_divider);
|
||||
|
||||
assertThat(shareButton).isNotNull();
|
||||
assertThat(shareButton.getVisibility()).isEqualTo(View.GONE);
|
||||
assertThat(shareButton.hasOnClickListeners()).isFalse();
|
||||
assertThat(divider).isNotNull();
|
||||
assertThat(divider.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_correctLayout_showQrCodeButton() {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
View view = inflater.inflate(mPreference.getLayoutResource(), null);
|
||||
LinearLayout widgetView = view.findViewById(android.R.id.widget_frame);
|
||||
assertThat(widgetView).isNotNull();
|
||||
inflater.inflate(mPreference.getWidgetLayoutResource(), widgetView, true);
|
||||
|
||||
var holder = PreferenceViewHolder.createInstanceForTests(view);
|
||||
mPreference.setShowQrCodeIcon(true);
|
||||
mPreference.onBindViewHolder(holder);
|
||||
|
||||
ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
|
||||
View divider =
|
||||
holder.findViewById(
|
||||
com.android.settingslib.widget.preference.twotarget.R.id
|
||||
.two_target_divider);
|
||||
|
||||
assertThat(shareButton).isNotNull();
|
||||
assertThat(shareButton.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
assertThat(shareButton.getDrawable()).isNotNull();
|
||||
assertThat(shareButton.hasOnClickListeners()).isTrue();
|
||||
assertThat(divider).isNotNull();
|
||||
assertThat(divider.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
|
||||
// mContext is not an Activity context, calling startActivity() from outside of an Activity
|
||||
// context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
|
||||
// AndroidRuntimeException.
|
||||
Context activityContext = mock(Context.class);
|
||||
when(mPreference.getContext()).thenReturn(activityContext);
|
||||
shareButton.callOnClick();
|
||||
|
||||
ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(activityContext).startActivity(argumentCaptor.capture());
|
||||
|
||||
Intent intent = argumentCaptor.getValue();
|
||||
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(AudioStreamsQrCodeFragment.class.getName());
|
||||
assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
|
||||
.isEqualTo(R.string.audio_streams_qr_code_page_title);
|
||||
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
|
||||
.isEqualTo(SettingsEnums.AUDIO_SHARING_SETTINGS);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioSharingNameTextValidatorTest {
|
||||
private AudioSharingNameTextValidator mValidator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mValidator = new AudioSharingNameTextValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidNames() {
|
||||
assertThat(mValidator.isTextValid("ValidName")).isTrue();
|
||||
assertThat(mValidator.isTextValid("12345678")).isTrue();
|
||||
assertThat(mValidator.isTextValid("Name_With_Underscores")).isTrue();
|
||||
assertThat(mValidator.isTextValid("ÄÖÜß")).isTrue();
|
||||
assertThat(mValidator.isTextValid("ThisNameIsExactly32Characters!")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidNames() {
|
||||
assertThat(mValidator.isTextValid(null)).isFalse();
|
||||
assertThat(mValidator.isTextValid("")).isFalse();
|
||||
assertThat(mValidator.isTextValid("abc")).isFalse();
|
||||
assertThat(mValidator.isTextValid("ThisNameIsWayTooLongForAnAudioSharingName")).isFalse();
|
||||
assertThat(mValidator.isTextValid("Invalid\uDC00")).isFalse();
|
||||
}
|
||||
}
|
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothStatusCodes;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
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.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.flags.Flags;
|
||||
|
||||
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.Spy;
|
||||
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.ShadowLooper;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowBluetoothAdapter.class,
|
||||
ShadowBluetoothUtils.class,
|
||||
})
|
||||
public class AudioSharingPasswordPreferenceControllerTest {
|
||||
private static final String PREF_KEY = "audio_sharing_stream_password";
|
||||
private static final String SHARED_PREF_KEY = "default_password";
|
||||
private static final String BROADCAST_PASSWORD = "password";
|
||||
private static final String EDITTEXT_PASSWORD = "edittext_password";
|
||||
private static final String HIDDEN_PASSWORD = "********";
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
@Spy Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private LocalBluetoothLeBroadcast mBroadcast;
|
||||
@Mock private LocalBluetoothManager mLocalBtManager;
|
||||
@Mock private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock private SharedPreferences mSharedPreferences;
|
||||
@Mock private SharedPreferences.Editor mEditor;
|
||||
@Mock private ContentResolver mContentResolver;
|
||||
@Mock private PreferenceScreen mScreen;
|
||||
private AudioSharingPasswordPreferenceController mController;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
private Lifecycle mLifecycle;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private AudioSharingPasswordPreference mPreference;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mShadowBluetoothAdapter.setEnabled(true);
|
||||
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
|
||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||
mLocalBtManager = Utils.getLocalBtManager(mContext);
|
||||
when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager);
|
||||
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
|
||||
when(mSharedPreferences.edit()).thenReturn(mEditor);
|
||||
when(mEditor.putString(anyString(), anyString())).thenReturn(mEditor);
|
||||
mLifecycleOwner = () -> mLifecycle;
|
||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||
mController = new AudioSharingPasswordPreferenceController(mContext, PREF_KEY);
|
||||
mPreference = spy(new AudioSharingPasswordPreference(mContext));
|
||||
when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus_flagOn_available() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus_flagOff_unsupported() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_flagOff_doNothing() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mContentResolver, never()).registerContentObserver(any(), anyBoolean(), any());
|
||||
verify(mSharedPreferences, never()).registerOnSharedPreferenceChangeListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_flagOn_registerCallbacks() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
|
||||
verify(mSharedPreferences).registerOnSharedPreferenceChangeListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_flagOff_doNothing() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStop(mLifecycleOwner);
|
||||
verify(mContentResolver, never()).unregisterContentObserver(any());
|
||||
verify(mSharedPreferences, never()).unregisterOnSharedPreferenceChangeListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_flagOn_registerCallbacks() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mController.onStop(mLifecycleOwner);
|
||||
verify(mContentResolver).unregisterContentObserver(any());
|
||||
verify(mSharedPreferences).unregisterOnSharedPreferenceChangeListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_setupPreference_noPassword() {
|
||||
when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
|
||||
when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreference.isPassword()).isTrue();
|
||||
assertThat(mPreference.getDialogLayoutResource())
|
||||
.isEqualTo(R.layout.audio_sharing_password_dialog);
|
||||
assertThat(mPreference.getText()).isEqualTo(EDITTEXT_PASSWORD);
|
||||
assertThat(mPreference.getSummary())
|
||||
.isEqualTo(mContext.getString(R.string.audio_streams_no_password_summary));
|
||||
verify(mPreference).setValidator(any());
|
||||
verify(mPreference).setOnDialogEventListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentObserver_updatePreferenceOnChange() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mBroadcast.getBroadcastCode())
|
||||
.thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
mController.onStart(mLifecycleOwner);
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
ArgumentCaptor<ContentObserver> observerCaptor =
|
||||
ArgumentCaptor.forClass(ContentObserver.class);
|
||||
verify(mContentResolver)
|
||||
.registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
|
||||
|
||||
var observer = observerCaptor.getValue();
|
||||
assertThat(observer).isNotNull();
|
||||
observer.onChange(true);
|
||||
verify(mPreference).setText(anyString());
|
||||
verify(mPreference).setSummary(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sharedPrefChangeListener_updatePreferenceOnChange() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mBroadcast.getBroadcastCode())
|
||||
.thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
mController.onStart(mLifecycleOwner);
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
ArgumentCaptor<SharedPreferences.OnSharedPreferenceChangeListener> captor =
|
||||
ArgumentCaptor.forClass(SharedPreferences.OnSharedPreferenceChangeListener.class);
|
||||
verify(mSharedPreferences).registerOnSharedPreferenceChangeListener(captor.capture());
|
||||
|
||||
var observer = captor.getValue();
|
||||
assertThat(captor).isNotNull();
|
||||
observer.onSharedPreferenceChanged(mSharedPreferences, SHARED_PREF_KEY);
|
||||
verify(mPreference).setText(anyString());
|
||||
verify(mPreference).setSummary(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_setupPreference_hasPassword() {
|
||||
when(mBroadcast.getBroadcastCode())
|
||||
.thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreference.isPassword()).isTrue();
|
||||
assertThat(mPreference.getDialogLayoutResource())
|
||||
.isEqualTo(R.layout.audio_sharing_password_dialog);
|
||||
assertThat(mPreference.getText()).isEqualTo(BROADCAST_PASSWORD);
|
||||
assertThat(mPreference.getSummary()).isEqualTo(HIDDEN_PASSWORD);
|
||||
verify(mPreference).setValidator(any());
|
||||
verify(mPreference).setOnDialogEventListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindDialogView_updatePreference_isBroadcasting_noPassword() {
|
||||
when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(true);
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onBindDialogView();
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mPreference).setEditable(false);
|
||||
verify(mPreference).setChecked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindDialogView_updatePreference_isNotBroadcasting_hasPassword() {
|
||||
when(mBroadcast.getBroadcastCode())
|
||||
.thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onBindDialogView();
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mPreference).setEditable(true);
|
||||
verify(mPreference).setChecked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceDataChanged_isBroadcasting_doNothing() {
|
||||
when(mBroadcast.isEnabled(any())).thenReturn(true);
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onPreferenceDataChanged(BROADCAST_PASSWORD, /* isPublicBroadcast= */ false);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast, never()).setBroadcastCode(any());
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceDataChanged_noChange_doNothing() {
|
||||
when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
|
||||
when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onPreferenceDataChanged(EDITTEXT_PASSWORD, /* isPublicBroadcast= */ true);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast, never()).setBroadcastCode(any());
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceDataChanged_updateToNonPublicBroadcast() {
|
||||
when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
|
||||
when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onPreferenceDataChanged(BROADCAST_PASSWORD, /* isPublicBroadcast= */ false);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast).setBroadcastCode(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
verify(mEditor).putString(anyString(), eq(BROADCAST_PASSWORD));
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceDataChanged_updateToPublicBroadcast() {
|
||||
when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
|
||||
when(mBroadcast.getBroadcastCode())
|
||||
.thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onPreferenceDataChanged(EDITTEXT_PASSWORD, /* isPublicBroadcast= */ true);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mBroadcast).setBroadcastCode("".getBytes(StandardCharsets.UTF_8));
|
||||
verify(mEditor, never()).putString(anyString(), eq(EDITTEXT_PASSWORD));
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idTextValid_emptyString() {
|
||||
boolean valid = mController.isTextValid("");
|
||||
|
||||
assertThat(valid).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idTextValid_validPassword() {
|
||||
boolean valid = mController.isTextValid(BROADCAST_PASSWORD);
|
||||
|
||||
assertThat(valid).isTrue();
|
||||
}
|
||||
}
|
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
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 AudioSharingPasswordPreferenceTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final String EDIT_TEXT_CONTENT = "text";
|
||||
private Context mContext;
|
||||
private AudioSharingPasswordPreference mPreference;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mPreference = new AudioSharingPasswordPreference(mContext, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindDialogView_correctLayout() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
var editText = view.findViewById(android.R.id.edit);
|
||||
var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
|
||||
var dialogMessage = view.findViewById(android.R.id.message);
|
||||
|
||||
assertThat(editText).isNotNull();
|
||||
assertThat(checkBox).isNotNull();
|
||||
assertThat(dialogMessage).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEditable_true() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
var editText = view.findViewById(android.R.id.edit);
|
||||
var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
|
||||
var dialogMessage = view.findViewById(android.R.id.message);
|
||||
|
||||
mPreference.setEditable(true);
|
||||
|
||||
assertThat(editText).isNotNull();
|
||||
assertThat(editText.isEnabled()).isTrue();
|
||||
assertThat(editText.getAlpha()).isEqualTo(1.0f);
|
||||
assertThat(checkBox).isNotNull();
|
||||
assertThat(checkBox.isEnabled()).isTrue();
|
||||
assertThat(dialogMessage).isNotNull();
|
||||
assertThat(dialogMessage.getVisibility()).isEqualTo(GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEditable_false() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
var editText = view.findViewById(android.R.id.edit);
|
||||
var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
|
||||
var dialogMessage = view.findViewById(android.R.id.message);
|
||||
|
||||
mPreference.setEditable(false);
|
||||
|
||||
assertThat(editText).isNotNull();
|
||||
assertThat(editText.isEnabled()).isFalse();
|
||||
assertThat(editText.getAlpha()).isLessThan(1.0f);
|
||||
assertThat(checkBox).isNotNull();
|
||||
assertThat(checkBox.isEnabled()).isFalse();
|
||||
assertThat(dialogMessage).isNotNull();
|
||||
assertThat(dialogMessage.getVisibility()).isEqualTo(VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_true() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
CheckBox checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
|
||||
|
||||
mPreference.setChecked(true);
|
||||
|
||||
assertThat(checkBox).isNotNull();
|
||||
assertThat(checkBox.isChecked()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_false() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
CheckBox checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
|
||||
|
||||
mPreference.setChecked(false);
|
||||
|
||||
assertThat(checkBox).isNotNull();
|
||||
assertThat(checkBox.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDialogEventListener_onClick_positiveButton() {
|
||||
AudioSharingPasswordPreference.OnDialogEventListener listener =
|
||||
mock(AudioSharingPasswordPreference.OnDialogEventListener.class);
|
||||
mPreference.setOnDialogEventListener(listener);
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
EditText editText = view.findViewById(android.R.id.edit);
|
||||
assertThat(editText).isNotNull();
|
||||
editText.setText(EDIT_TEXT_CONTENT);
|
||||
|
||||
mPreference.onClick(mock(DialogInterface.class), DialogInterface.BUTTON_POSITIVE);
|
||||
|
||||
verify(listener).onBindDialogView();
|
||||
verify(listener).onPreferenceDataChanged(eq(EDIT_TEXT_CONTENT), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDialogEventListener_onClick_negativeButton_doNothing() {
|
||||
AudioSharingPasswordPreference.OnDialogEventListener listener =
|
||||
mock(AudioSharingPasswordPreference.OnDialogEventListener.class);
|
||||
mPreference.setOnDialogEventListener(listener);
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
|
||||
EditText editText = view.findViewById(android.R.id.edit);
|
||||
assertThat(editText).isNotNull();
|
||||
editText.setText(EDIT_TEXT_CONTENT);
|
||||
|
||||
mPreference.onClick(mock(DialogInterface.class), DialogInterface.BUTTON_NEGATIVE);
|
||||
|
||||
verify(listener).onBindDialogView();
|
||||
verify(listener, never()).onPreferenceDataChanged(anyString(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPrepareDialogBuilder_editable_doNothing() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
mPreference.setEditable(true);
|
||||
|
||||
var dialogBuilder = mock(AlertDialog.Builder.class);
|
||||
mPreference.onPrepareDialogBuilder(
|
||||
dialogBuilder, mock(DialogInterface.OnClickListener.class));
|
||||
|
||||
verify(dialogBuilder, never()).setPositiveButton(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPrepareDialogBuilder_notEditable_disableButton() {
|
||||
View view =
|
||||
LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
|
||||
mPreference.onBindDialogView(view);
|
||||
mPreference.setEditable(false);
|
||||
|
||||
var dialogBuilder = mock(AlertDialog.Builder.class);
|
||||
mPreference.onPrepareDialogBuilder(
|
||||
dialogBuilder, mock(DialogInterface.OnClickListener.class));
|
||||
|
||||
verify(dialogBuilder).setPositiveButton(any(), any());
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioSharingPasswordValidatorTest {
|
||||
private AudioSharingPasswordValidator mValidator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mValidator = new AudioSharingPasswordValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidPasswords() {
|
||||
assertThat(mValidator.isTextValid("1234")).isTrue();
|
||||
assertThat(mValidator.isTextValid("Password")).isTrue();
|
||||
assertThat(mValidator.isTextValid("SecurePass123!")).isTrue();
|
||||
assertThat(mValidator.isTextValid("ÄÖÜß")).isTrue();
|
||||
assertThat(mValidator.isTextValid("1234567890abcdef")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidPasswords() {
|
||||
assertThat(mValidator.isTextValid(null)).isFalse();
|
||||
assertThat(mValidator.isTextValid("")).isFalse();
|
||||
assertThat(mValidator.isTextValid("abc")).isFalse();
|
||||
assertThat(mValidator.isTextValid("ThisIsAVeryLongPasswordThatExceedsSixteenOctets"))
|
||||
.isFalse();
|
||||
assertThat(mValidator.isTextValid("Invalid\uDC00")).isFalse();
|
||||
}
|
||||
}
|
@@ -20,18 +20,40 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Add
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
|
||||
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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AddSourceBadCodeStateTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private AudioStreamPreference mPreference;
|
||||
@Mock private AudioStreamsProgressCategoryController mController;
|
||||
@Mock private AudioStreamsHelper mHelper;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
private AddSourceBadCodeState mInstance;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mInstance = AddSourceBadCodeState.getInstance();
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mInstance = new AddSourceBadCodeState();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,4 +77,19 @@ public class AddSourceBadCodeStateTest {
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformAction() {
|
||||
when(mPreference.getContext()).thenReturn(mContext);
|
||||
when(mPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
|
||||
mInstance.performAction(mPreference, mController, mHelper);
|
||||
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_BAD_CODE),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
}
|
||||
}
|
||||
|
@@ -20,18 +20,40 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Add
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
|
||||
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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AddSourceFailedStateTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private AudioStreamPreference mPreference;
|
||||
@Mock private AudioStreamsProgressCategoryController mController;
|
||||
@Mock private AudioStreamsHelper mHelper;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
private AddSourceFailedState mInstance;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mInstance = AddSourceFailedState.getInstance();
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mInstance = new AddSourceFailedState();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,4 +76,19 @@ public class AddSourceFailedStateTest {
|
||||
.isEqualTo(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformAction() {
|
||||
when(mPreference.getContext()).thenReturn(mContext);
|
||||
when(mPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
|
||||
mInstance.performAction(mPreference, mController, mHelper);
|
||||
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_OTHER),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
}
|
||||
}
|
||||
|
@@ -22,11 +22,21 @@ 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.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -36,23 +46,36 @@ 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.shadows.ShadowAlertDialog;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowAlertDialog.class,
|
||||
})
|
||||
public class AddSourceWaitForResponseStateTest {
|
||||
private static final int BROADCAST_ID = 1;
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final int BROADCAST_ID = 1;
|
||||
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
@Mock private AudioStreamPreference mMockPreference;
|
||||
@Mock private AudioStreamsProgressCategoryController mMockController;
|
||||
@Mock private AudioStreamsHelper mMockHelper;
|
||||
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
|
||||
@Mock private AudioStreamsRepository mMockRepository;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
private AddSourceWaitForResponseState mInstance;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mInstance = AddSourceWaitForResponseState.getInstance();
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mInstance = new AddSourceWaitForResponseState();
|
||||
when(mMockPreference.getContext()).thenReturn(mContext);
|
||||
when(mMockPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,11 +116,18 @@ public class AddSourceWaitForResponseStateTest {
|
||||
public void testPerformAction_metadataIsNotNull_addSource() {
|
||||
when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
|
||||
when(mMockPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.UNKNOWN);
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
mInstance.setAudioStreamsRepositoryForTesting(mMockRepository);
|
||||
|
||||
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
|
||||
|
||||
verify(mMockHelper).addSource(mMockMetadata);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
verify(mMockRepository).cacheMetadata(mMockMetadata);
|
||||
verify(mMockController, never()).handleSourceFailedToConnect(anyInt());
|
||||
}
|
||||
|
||||
@@ -108,12 +138,28 @@ public class AddSourceWaitForResponseStateTest {
|
||||
when(mMockPreference.getAudioStreamState()).thenReturn(mInstance.getStateEnum());
|
||||
when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
|
||||
when(mMockPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.UNKNOWN);
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class));
|
||||
mInstance.setAudioStreamsRepositoryForTesting(mMockRepository);
|
||||
|
||||
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
|
||||
ShadowLooper.idleMainLooper(ADD_SOURCE_WAIT_FOR_RESPONSE_TIMEOUT_MILLIS, TimeUnit.SECONDS);
|
||||
|
||||
verify(mMockHelper).addSource(mMockMetadata);
|
||||
verify(mMockController).handleSourceFailedToConnect(BROADCAST_ID);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
verify(mMockRepository).cacheMetadata(mMockMetadata);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_TIMEOUT),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available);
|
||||
verify(mContext).getString(R.string.audio_streams_is_not_playing);
|
||||
verify(mContext).getString(R.string.audio_streams_dialog_close);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamStateHandlerTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final int SUMMARY_RES = 1;
|
||||
private static final String SUMMARY = "summary";
|
||||
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
@Mock private AudioStreamsProgressCategoryController mController;
|
||||
@Mock private AudioStreamsHelper mHelper;
|
||||
@Mock private AudioStreamPreference mPreference;
|
||||
private AudioStreamStateHandler mHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mHandler = spy(new AudioStreamStateHandler());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleStateChange_noChange_doNothing() {
|
||||
when(mHandler.getStateEnum())
|
||||
.thenReturn(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
when(mPreference.getAudioStreamState())
|
||||
.thenReturn(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
|
||||
mHandler.handleStateChange(mPreference, mController, mHelper);
|
||||
|
||||
verify(mPreference, never()).setAudioStreamState(any());
|
||||
verify(mHandler, never()).performAction(any(), any(), any());
|
||||
verify(mPreference, never()).setIsConnected(anyBoolean(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleStateChange_setNewState() {
|
||||
when(mHandler.getStateEnum())
|
||||
.thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
|
||||
when(mPreference.getAudioStreamState())
|
||||
.thenReturn(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
|
||||
mHandler.handleStateChange(mPreference, mController, mHelper);
|
||||
|
||||
verify(mPreference)
|
||||
.setAudioStreamState(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
|
||||
verify(mHandler).performAction(any(), any(), any());
|
||||
verify(mPreference).setIsConnected(eq(true), eq(""), eq(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleStateChange_setNewState_newSummary_newListener() {
|
||||
Preference.OnPreferenceClickListener listener =
|
||||
mock(Preference.OnPreferenceClickListener.class);
|
||||
when(mHandler.getStateEnum())
|
||||
.thenReturn(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
when(mHandler.getSummary()).thenReturn(SUMMARY_RES);
|
||||
when(mHandler.getOnClickListener(any())).thenReturn(listener);
|
||||
when(mPreference.getAudioStreamState())
|
||||
.thenReturn(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED);
|
||||
when(mPreference.getContext()).thenReturn(mContext);
|
||||
doReturn(SUMMARY).when(mContext).getString(anyInt());
|
||||
|
||||
mHandler.handleStateChange(mPreference, mController, mHelper);
|
||||
|
||||
verify(mPreference)
|
||||
.setAudioStreamState(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState
|
||||
.ADD_SOURCE_BAD_CODE);
|
||||
verify(mHandler).performAction(any(), any(), any());
|
||||
verify(mPreference).setIsConnected(eq(false), eq(SUMMARY), eq(listener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSummary() {
|
||||
int res = mHandler.getSummary();
|
||||
assertThat(res).isEqualTo(AudioStreamStateHandler.EMPTY_STRING_RES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOnClickListener() {
|
||||
Preference.OnPreferenceClickListener listener = mHandler.getOnClickListener(mController);
|
||||
assertThat(listener).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStateEnum() {
|
||||
var state = mHandler.getStateEnum();
|
||||
assertThat(state)
|
||||
.isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN);
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
@@ -76,25 +77,46 @@ public class AudioStreamsActiveDeviceSummaryUpdaterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_notLeProfile_doNothing() {
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, 0);
|
||||
public void unregister_doNothing() {
|
||||
mUpdater.register(false);
|
||||
|
||||
assertThat(mUpdatedSummary).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_leProfile_summaryUpdated() {
|
||||
public void onProfileConnectionStateChanged_notLeAssistProfile_doNothing() {
|
||||
mUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, 0, 0);
|
||||
|
||||
assertThat(mUpdatedSummary).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_leAssistantProfile_summaryUpdated() {
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
|
||||
mCachedBluetoothDevice);
|
||||
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
|
||||
mUpdater.onProfileConnectionStateChanged(
|
||||
mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||
|
||||
assertThat(mUpdatedSummary).isEqualTo(DEVICE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_leProfile_noDevice_summaryUpdated() {
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
|
||||
public void onActiveDeviceChanged_leAssistantProfile_noDevice_summaryUpdated() {
|
||||
mUpdater.onProfileConnectionStateChanged(
|
||||
mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||
|
||||
assertThat(mUpdatedSummary)
|
||||
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBluetoothStateOff_summaryUpdated() {
|
||||
mUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
||||
|
||||
assertThat(mUpdatedSummary)
|
||||
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
|
||||
|
@@ -23,11 +23,13 @@ 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.spy;
|
||||
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.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothStatusCodes;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
@@ -42,6 +44,7 @@ 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.BluetoothCallback;
|
||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
|
||||
@@ -57,6 +60,7 @@ 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;
|
||||
@@ -116,7 +120,7 @@ public class AudioStreamsCategoryControllerTest {
|
||||
when(mBroadcast.isProfileReady()).thenReturn(true);
|
||||
when(mAssistant.isProfileReady()).thenReturn(true);
|
||||
when(mVolumeControl.isProfileReady()).thenReturn(true);
|
||||
mController = new AudioStreamsCategoryController(mContext, KEY);
|
||||
mController = spy(new AudioStreamsCategoryController(mContext, KEY));
|
||||
mPreference = new Preference(mContext);
|
||||
when(mScreen.findPreference(KEY)).thenReturn(mPreference);
|
||||
mController.displayPreference(mScreen);
|
||||
@@ -228,4 +232,21 @@ public class AudioStreamsCategoryControllerTest {
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_updateVisibility() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
|
||||
ArgumentCaptor<BluetoothCallback> argumentCaptor =
|
||||
ArgumentCaptor.forClass(BluetoothCallback.class);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
verify(mBluetoothEventManager).registerCallback(argumentCaptor.capture());
|
||||
|
||||
BluetoothCallback callback = argumentCaptor.getValue();
|
||||
callback.onProfileConnectionStateChanged(
|
||||
mCachedBluetoothDevice,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
|
||||
BluetoothAdapter.STATE_DISCONNECTED);
|
||||
|
||||
verify(mController).updateVisibility();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.verify;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowAlertDialog;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowAlertDialog.class,
|
||||
})
|
||||
public class AudioStreamsDialogFragmentTest {
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private AudioStreamsDialogFragment.DialogBuilder mDialogBuilder;
|
||||
private AudioStreamsDialogFragment mFragment;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mDialogBuilder = spy(new AudioStreamsDialogFragment.DialogBuilder(mContext));
|
||||
mFragment = new AudioStreamsDialogFragment(mDialogBuilder, SettingsEnums.PAGE_UNKNOWN);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ShadowAlertDialog.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMetricsCategory() {
|
||||
int dialogId = mFragment.getMetricsCategory();
|
||||
|
||||
assertThat(dialogId).isEqualTo(SettingsEnums.PAGE_UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCreateDialog() {
|
||||
mFragment.onCreateDialog(Bundle.EMPTY);
|
||||
|
||||
verify(mDialogBuilder).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShowDialog() {
|
||||
FragmentController.setupFragment(mFragment);
|
||||
AudioStreamsDialogFragment.show(mFragment, mDialogBuilder, SettingsEnums.PAGE_UNKNOWN);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
var dialog = ShadowAlertDialog.getLatestAlertDialog();
|
||||
assertThat(dialog).isNotNull();
|
||||
assertThat(dialog.isShowing()).isTrue();
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamsProgressCategoryCallbackTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Mock private AudioStreamsProgressCategoryController mController;
|
||||
@Mock private BluetoothDevice mDevice;
|
||||
@Mock private BluetoothLeBroadcastReceiveState mState;
|
||||
@Mock private BluetoothLeBroadcastMetadata mMetadata;
|
||||
private AudioStreamsProgressCategoryCallback mCallback;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mCallback = new AudioStreamsProgressCategoryCallback(mController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnReceiveStateChanged_connected() {
|
||||
List<Long> bisSyncState = new ArrayList<>();
|
||||
bisSyncState.add(1L);
|
||||
when(mState.getBisSyncState()).thenReturn(bisSyncState);
|
||||
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
|
||||
|
||||
verify(mController).handleSourceConnected(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnReceiveStateChanged_badCode() {
|
||||
when(mState.getPaSyncState())
|
||||
.thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
|
||||
when(mState.getBigEncryptionState())
|
||||
.thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE);
|
||||
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
|
||||
|
||||
verify(mController).handleSourceConnectBadCode(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSearchStartFailed() {
|
||||
mCallback.onSearchStartFailed(/* reason= */ 0);
|
||||
|
||||
verify(mController).showToast(anyString());
|
||||
verify(mController).setScanning(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSearchStarted() {
|
||||
mCallback.onSearchStarted(/* reason= */ 0);
|
||||
|
||||
verify(mController).setScanning(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSearchStopFailed() {
|
||||
mCallback.onSearchStopFailed(/* reason= */ 0);
|
||||
|
||||
verify(mController).showToast(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSearchStopped() {
|
||||
mCallback.onSearchStopped(/* reason= */ 0);
|
||||
|
||||
verify(mController).setScanning(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSourceAddFailed() {
|
||||
when(mMetadata.getBroadcastId()).thenReturn(1);
|
||||
mCallback.onSourceAddFailed(mDevice, mMetadata, /* reason= */ 0);
|
||||
|
||||
verify(mController).handleSourceFailedToConnect(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSourceFound() {
|
||||
mCallback.onSourceFound(mMetadata);
|
||||
|
||||
verify(mController).handleSourceFound(mMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSourceLost() {
|
||||
mCallback.onSourceLost(/* broadcastId= */ 1);
|
||||
|
||||
verify(mController).handleSourceLost(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSourceRemoveFailed() {
|
||||
mCallback.onSourceRemoveFailed(mDevice, /* sourceId= */ 0, /* reason= */ 0);
|
||||
|
||||
verify(mController).showToast(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSourceRemoved() {
|
||||
mCallback.onSourceRemoved(mDevice, /* sourceId= */ 0, /* reason= */ 0);
|
||||
|
||||
verify(mController).handleSourceRemoved();
|
||||
}
|
||||
}
|
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
* 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.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_BAD_CODE;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SYNCED;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
|
||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothLeAudioContentMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
|
||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||
import com.android.settings.testutils.shadow.ShadowThreadUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
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.shadows.ShadowAlertDialog;
|
||||
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowBluetoothUtils.class,
|
||||
ShadowAudioStreamsHelper.class,
|
||||
ShadowThreadUtils.class,
|
||||
ShadowAlertDialog.class,
|
||||
})
|
||||
public class AudioStreamsProgressCategoryControllerTest {
|
||||
@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 static final String KEY = "audio_streams_nearby_category";
|
||||
private static final int QR_CODE_BROADCAST_ID = 1;
|
||||
private static final int ALREADY_CONNECTED_BROADCAST_ID = 2;
|
||||
private static final int NEWLY_FOUND_BROADCAST_ID = 3;
|
||||
private static final String BROADCAST_NAME_1 = "name_1";
|
||||
private static final String BROADCAST_NAME_2 = "name_2";
|
||||
private static final byte[] BROADCAST_CODE = new byte[] {1};
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private LocalBluetoothManager mLocalBtManager;
|
||||
@Mock private BluetoothEventManager mBluetoothEventManager;
|
||||
@Mock private PreferenceScreen mScreen;
|
||||
@Mock private AudioStreamsHelper mAudioStreamsHelper;
|
||||
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||
@Mock private BluetoothLeBroadcastMetadata mMetadata;
|
||||
@Mock private CachedBluetoothDevice mDevice;
|
||||
@Mock private AudioStreamsProgressCategoryPreference mPreference;
|
||||
private Lifecycle mLifecycle;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private Fragment mFragment;
|
||||
private TestController mController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
|
||||
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
|
||||
|
||||
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
|
||||
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
|
||||
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false);
|
||||
|
||||
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
|
||||
|
||||
mLifecycleOwner = () -> mLifecycle;
|
||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||
|
||||
mFragment = new Fragment();
|
||||
mController = spy(new TestController(mContext, KEY));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ShadowBluetoothUtils.reset();
|
||||
ShadowAudioStreamsHelper.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAvailabilityStatus() {
|
||||
int status = mController.getAvailabilityStatus();
|
||||
|
||||
assertThat(status).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayPreference() {
|
||||
mController.displayPreference(mScreen);
|
||||
|
||||
verify(mPreference).setVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScanning() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.setScanning(true);
|
||||
|
||||
verify(mPreference).setProgress(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStart_initNoDevice_showDialog() {
|
||||
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(true);
|
||||
|
||||
FragmentController.setupFragment(mFragment);
|
||||
mController.setFragment(mFragment);
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// Called twice, once in displayPreference, the other in init()
|
||||
verify(mPreference, times(2)).setVisible(anyBoolean());
|
||||
verify(mPreference).removeAudioStreamPreferences();
|
||||
verify(mLeBroadcastAssistant).stopSearchingForSources();
|
||||
verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
|
||||
|
||||
var dialog = ShadowAlertDialog.getLatestAlertDialog();
|
||||
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();
|
||||
|
||||
dialog.cancel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBluetoothOff_triggerRunnable() {
|
||||
mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
|
||||
|
||||
verify(mController.mExecutor).execute(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeviceConnectionStateChanged_triggerRunnable() {
|
||||
mController.mBluetoothCallback.onProfileConnectionStateChanged(
|
||||
mDevice,
|
||||
BluetoothAdapter.STATE_DISCONNECTED,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||
|
||||
verify(mController.mExecutor).execute(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStart_initHasDevice_noPreference() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
verify(mLeBroadcastAssistant).registerServiceCallBack(any(), any());
|
||||
verify(mLeBroadcastAssistant).startSearchingForSources(any());
|
||||
|
||||
var dialog = ShadowAlertDialog.getLatestAlertDialog();
|
||||
assertThat(dialog).isNull();
|
||||
|
||||
verify(mController, never()).moveToState(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStart_handleSourceFromQrCode() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup a source from qr code
|
||||
mController.setSourceFromQrCode(mMetadata, SourceOriginForLogging.UNKNOWN);
|
||||
when(mMetadata.getBroadcastId()).thenReturn(QR_CODE_BROADCAST_ID);
|
||||
|
||||
// Handle the source from qr code in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// Verify the connected source is created and moved to WAIT_FOR_SYNC
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController).moveToState(preference.capture(), state.capture());
|
||||
assertThat(preference.getValue()).isNotNull();
|
||||
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(QR_CODE_BROADCAST_ID);
|
||||
assertThat(state.getValue()).isEqualTo(WAIT_FOR_SYNC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStart_handleSourceAlreadyConnected() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup a connected source
|
||||
BluetoothLeBroadcastReceiveState connected =
|
||||
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
List<BluetoothLeBroadcastReceiveState> list = new ArrayList<>();
|
||||
list.add(connected);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(list);
|
||||
|
||||
// Handle already connected source in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
// Verify the connected source is created and moved to SOURCE_ADDED
|
||||
verify(mController).moveToState(preference.capture(), state.capture());
|
||||
assertThat(preference.getValue()).isNotNull();
|
||||
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStart_sourceFromQrCodeNoId_sourceAlreadyConnected_sameName_updateId() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup source from qr code with unset id and BROADCAST_NAME_1. Creating a real metadata
|
||||
// for properly update its id.
|
||||
var metadata =
|
||||
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
|
||||
assertThat(metadata).isNotNull();
|
||||
var metadataWithNoIdAndSameName =
|
||||
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||
.setBroadcastId(UNSET_BROADCAST_ID)
|
||||
.setBroadcastName(BROADCAST_NAME_1)
|
||||
.build();
|
||||
mController.setSourceFromQrCode(
|
||||
metadataWithNoIdAndSameName, SourceOriginForLogging.UNKNOWN);
|
||||
|
||||
// Setup a connected source with name BROADCAST_NAME_1 and id
|
||||
BluetoothLeBroadcastReceiveState connected =
|
||||
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
var data = mock(BluetoothLeAudioContentMetadata.class);
|
||||
when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data));
|
||||
when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||
|
||||
// Handle both source from qr code and already connected source in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// Verify two preferences created, one moved to state WAIT_FOR_SYNC, one to SOURCE_ADDED.
|
||||
// Both has ALREADY_CONNECTED_BROADCAST_ID as the UNSET_ID is updated to match.
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
verify(mController, times(2)).moveToState(preference.capture(), state.capture());
|
||||
|
||||
List<AudioStreamPreference> preferences = preference.getAllValues();
|
||||
assertThat(preferences.size()).isEqualTo(2);
|
||||
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
|
||||
assertThat(states.size()).isEqualTo(2);
|
||||
|
||||
// The preference contains source from qr code
|
||||
assertThat(preferences.get(0).getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
|
||||
|
||||
// The preference contains already connected source
|
||||
assertThat(preferences.get(1).getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(states.get(1)).isEqualTo(SOURCE_ADDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceFound_addNew() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
|
||||
// A new source is found
|
||||
mController.handleSourceFound(mMetadata);
|
||||
|
||||
// Verify a preference is created with state SYNCED.
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController).moveToState(preference.capture(), state.capture());
|
||||
assertThat(preference.getValue()).isNotNull();
|
||||
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||
assertThat(state.getValue()).isEqualTo(SYNCED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceFound_sameIdWithSourceFromQrCode_updateMetadataAndState() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup source from qr code with QR_CODE_BROADCAST_ID, BROADCAST_NAME_1 and BROADCAST_CODE.
|
||||
var metadata =
|
||||
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
|
||||
assertThat(metadata).isNotNull();
|
||||
var metadataFromQrCode =
|
||||
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||
.setBroadcastId(QR_CODE_BROADCAST_ID)
|
||||
.setBroadcastName(BROADCAST_NAME_1)
|
||||
.setBroadcastCode(BROADCAST_CODE)
|
||||
.build();
|
||||
mController.setSourceFromQrCode(metadataFromQrCode, SourceOriginForLogging.UNKNOWN);
|
||||
|
||||
// Handle the source from qr code in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// A new source is found
|
||||
mController.handleSourceFound(
|
||||
new BluetoothLeBroadcastMetadata.Builder(metadata)
|
||||
.setBroadcastId(QR_CODE_BROADCAST_ID)
|
||||
.setBroadcastName(BROADCAST_NAME_2)
|
||||
.build());
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController, times(2)).moveToState(preference.capture(), state.capture());
|
||||
List<AudioStreamPreference> preferences = preference.getAllValues();
|
||||
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
|
||||
|
||||
// Verify the qr code source is created with WAIT_FOR_SYNC, broadcast name got updated to
|
||||
// BROADCAST_NAME_2
|
||||
var sourceFromQrCode = preferences.get(0);
|
||||
assertThat(sourceFromQrCode.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
|
||||
assertThat(sourceFromQrCode.getAudioStreamMetadata()).isNotNull();
|
||||
assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastName())
|
||||
.isEqualTo(BROADCAST_NAME_2);
|
||||
assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastCode())
|
||||
.isEqualTo(BROADCAST_CODE);
|
||||
assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
|
||||
|
||||
// Verify the newly found source is created, broadcast code is retrieved from the source
|
||||
// from qr code, and state updated to ADD_SOURCE_WAIT_FOR_RESPONSE
|
||||
var newlyFoundSource = preferences.get(1);
|
||||
assertThat(newlyFoundSource.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
|
||||
assertThat(newlyFoundSource.getAudioStreamMetadata()).isNotNull();
|
||||
assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastName())
|
||||
.isEqualTo(BROADCAST_NAME_2);
|
||||
assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastCode())
|
||||
.isEqualTo(BROADCAST_CODE);
|
||||
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_WAIT_FOR_RESPONSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceFound_sameIdWithOtherState_doNothing() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup source already connected
|
||||
BluetoothLeBroadcastReceiveState connected =
|
||||
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||
|
||||
// Handle source already connected in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// A new source found
|
||||
when(mMetadata.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
mController.handleSourceFound(mMetadata);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// Verify only the connected source has created a preference, and its state remains as
|
||||
// SOURCE_ADDED
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController).moveToState(preference.capture(), state.capture());
|
||||
assertThat(preference.getValue()).isNotNull();
|
||||
assertThat(preference.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(preference.getValue().getAudioStreamState()).isEqualTo(SOURCE_ADDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceLost_removed() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup mPreference so it's not null
|
||||
mController.displayPreference(mScreen);
|
||||
|
||||
// A new source found
|
||||
when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
|
||||
mController.handleSourceFound(mMetadata);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// A new source found is lost
|
||||
mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
// Verify a new preference is created with state SYNCED.
|
||||
verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
|
||||
assertThat(preferenceToAdd.getValue()).isNotNull();
|
||||
assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||
assertThat(state.getValue()).isEqualTo(SYNCED);
|
||||
|
||||
// Verify the preference with NEWLY_FOUND_BROADCAST_ID is removed.
|
||||
verify(mPreference).removePreference(preferenceToRemove.capture());
|
||||
assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceRemoved_removed() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup already connected source
|
||||
BluetoothLeBroadcastReceiveState connected =
|
||||
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||
|
||||
// Handle connected source in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// The connect source is no longer connected
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
|
||||
mController.handleSourceRemoved();
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
// Verify a new preference is created with state SOURCE_ADDED.
|
||||
verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
|
||||
assertThat(preferenceToAdd.getValue()).isNotNull();
|
||||
assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
|
||||
|
||||
// Verify the preference with ALREADY_CONNECTED_BROADCAST_ID is removed.
|
||||
verify(mPreference).removePreference(preferenceToRemove.capture());
|
||||
assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceRemoved_updateState() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup a connected source
|
||||
BluetoothLeBroadcastReceiveState connected =
|
||||
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
|
||||
|
||||
// Handle connected source in onStart
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onStart(mLifecycleOwner);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// The connected source is identified as having a bad code
|
||||
BluetoothLeBroadcastReceiveState badCode = mock(BluetoothLeBroadcastReceiveState.class);
|
||||
when(badCode.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
when(badCode.getPaSyncState())
|
||||
.thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
|
||||
when(badCode.getBigEncryptionState())
|
||||
.thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE);
|
||||
mController.handleSourceConnectBadCode(badCode);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController, times(2)).moveToState(preference.capture(), state.capture());
|
||||
List<AudioStreamPreference> preferences = preference.getAllValues();
|
||||
assertThat(preferences.size()).isEqualTo(2);
|
||||
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
|
||||
assertThat(states.size()).isEqualTo(2);
|
||||
|
||||
// Verify the connected source is created state SOURCE_ADDED
|
||||
assertThat(preferences.get(0).getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(states.get(0)).isEqualTo(SOURCE_ADDED);
|
||||
|
||||
// Verify the connected source is updated to state ADD_SOURCE_BAD_CODE
|
||||
assertThat(preferences.get(1).getAudioStreamBroadcastId())
|
||||
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
|
||||
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_BAD_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleSourceFailedToConnect_updateState() {
|
||||
// Setup a device
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
|
||||
// Setup mPreference so it's not null
|
||||
mController.displayPreference(mScreen);
|
||||
|
||||
// A new source found
|
||||
when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
|
||||
mController.handleSourceFound(mMetadata);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
// The new found source is identified as failed to connect
|
||||
mController.handleSourceFailedToConnect(NEWLY_FOUND_BROADCAST_ID);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
ArgumentCaptor<AudioStreamPreference> preference =
|
||||
ArgumentCaptor.forClass(AudioStreamPreference.class);
|
||||
ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
|
||||
ArgumentCaptor.forClass(
|
||||
AudioStreamsProgressCategoryController.AudioStreamState.class);
|
||||
|
||||
verify(mController, times(2)).moveToState(preference.capture(), state.capture());
|
||||
List<AudioStreamPreference> preferences = preference.getAllValues();
|
||||
assertThat(preferences.size()).isEqualTo(2);
|
||||
List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
|
||||
assertThat(states.size()).isEqualTo(2);
|
||||
|
||||
// Verify one preference is created with SYNCED
|
||||
assertThat(preferences.get(0).getAudioStreamBroadcastId())
|
||||
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||
assertThat(states.get(0)).isEqualTo(SYNCED);
|
||||
|
||||
// Verify the preference is updated to state ADD_SOURCE_FAILED
|
||||
assertThat(preferences.get(1).getAudioStreamBroadcastId())
|
||||
.isEqualTo(NEWLY_FOUND_BROADCAST_ID);
|
||||
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_FAILED);
|
||||
}
|
||||
|
||||
private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
|
||||
var connected = mock(BluetoothLeBroadcastReceiveState.class);
|
||||
List<Long> bisSyncState = new ArrayList<>();
|
||||
bisSyncState.add(1L);
|
||||
when(connected.getBroadcastId()).thenReturn(id);
|
||||
when(connected.getBisSyncState()).thenReturn(bisSyncState);
|
||||
return connected;
|
||||
}
|
||||
|
||||
static class TestController extends AudioStreamsProgressCategoryController {
|
||||
TestController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mExecutor = spy(mContext.getMainExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
void moveToState(AudioStreamPreference preference, AudioStreamState state) {
|
||||
preference.setAudioStreamState(state);
|
||||
// Do nothing else to avoid side effect from AudioStreamStateHandler#performAction
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,29 +16,38 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
|
||||
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
|
||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
|
||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -46,6 +55,7 @@ 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;
|
||||
@@ -139,17 +149,46 @@ public class AudioStreamsScanQrCodeControllerTest {
|
||||
public void onPreferenceClick_hasFragment_launchSubSetting() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.setFragment(mFragment);
|
||||
when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
|
||||
|
||||
var listener = mPreference.getOnPreferenceClickListener();
|
||||
assertThat(listener).isNotNull();
|
||||
|
||||
// mContext is not an Activity context, calling startActivity() from outside of an Activity
|
||||
// context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
|
||||
// AndroidRuntimeException.
|
||||
Context activityContext = mock(Context.class);
|
||||
when(mPreference.getContext()).thenReturn(activityContext);
|
||||
when(mPreference.getKey()).thenReturn(AudioStreamsScanQrCodeController.KEY);
|
||||
|
||||
var clicked = listener.onPreferenceClick(mPreference);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<Integer> requestCodeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(mFragment)
|
||||
.startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture());
|
||||
|
||||
Intent intent = intentCaptor.getValue();
|
||||
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(AudioStreamsQrCodeScanFragment.class.getName());
|
||||
assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
|
||||
.isEqualTo(R.string.audio_streams_main_page_scan_qr_code_title);
|
||||
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
|
||||
.isEqualTo(AUDIO_STREAM_MAIN);
|
||||
|
||||
int requestCode = requestCodeCaptor.getValue();
|
||||
assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE);
|
||||
|
||||
assertThat(clicked).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVisibility_noConnected_invisible() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO);
|
||||
mController.mBluetoothCallback.onProfileConnectionStateChanged(
|
||||
mDevice,
|
||||
BluetoothAdapter.STATE_DISCONNECTED,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||
|
||||
assertThat(mPreference.isVisible()).isFalse();
|
||||
}
|
||||
@@ -158,7 +197,10 @@ public class AudioStreamsScanQrCodeControllerTest {
|
||||
public void updateVisibility_hasConnected_visible() {
|
||||
mController.displayPreference(mScreen);
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
|
||||
mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO);
|
||||
mController.mBluetoothCallback.onProfileConnectionStateChanged(
|
||||
mDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED,
|
||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
@@ -16,27 +16,71 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
|
||||
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourceAddedState.AUDIO_STREAM_SOURCE_ADDED_STATE_SUMMARY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
import com.android.settings.testutils.shadow.ShadowFragment;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowFragment.class,
|
||||
})
|
||||
public class SourceAddedStateTest {
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final int BROADCAST_ID = 1;
|
||||
private static final String BROADCAST_TITLE = "title";
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock private AudioStreamPreference mPreference;
|
||||
@Mock private AudioStreamsProgressCategoryController mController;
|
||||
@Mock private AudioStreamsHelper mHelper;
|
||||
@Mock private AudioStreamsRepository mRepository;
|
||||
@Mock private AudioStreamsDashboardFragment mFragment;
|
||||
@Mock private FragmentActivity mActivity;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
private SourceAddedState mInstance;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mInstance = SourceAddedState.getInstance();
|
||||
when(mFragment.getActivity()).thenReturn(mActivity);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mInstance = new SourceAddedState();
|
||||
when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
|
||||
when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -58,4 +102,59 @@ public class SourceAddedStateTest {
|
||||
assertThat(stateEnum)
|
||||
.isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformAction() {
|
||||
mInstance.setAudioStreamsRepositoryForTesting(mRepository);
|
||||
BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
|
||||
when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
|
||||
when(mPreference.getContext()).thenReturn(mContext);
|
||||
when(mPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
|
||||
|
||||
mInstance.performAction(mPreference, mController, mHelper);
|
||||
|
||||
verify(mRepository).saveMetadata(eq(mContext), eq(mockMetadata));
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED),
|
||||
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
|
||||
verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOnClickListener_startSubSettings() {
|
||||
when(mController.getFragment()).thenReturn(mFragment);
|
||||
when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
|
||||
|
||||
Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController);
|
||||
assertThat(listener).isNotNull();
|
||||
|
||||
// mContext is not an Activity context, calling startActivity() from outside of an Activity
|
||||
// context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
|
||||
// AndroidRuntimeException.
|
||||
Context activityContext = mock(Context.class);
|
||||
when(mPreference.getContext()).thenReturn(activityContext);
|
||||
|
||||
listener.onPreferenceClick(mPreference);
|
||||
|
||||
ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(activityContext).startActivity(argumentCaptor.capture());
|
||||
|
||||
Intent intent = argumentCaptor.getValue();
|
||||
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(AudioStreamDetailsFragment.class.getName());
|
||||
assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
|
||||
.isEqualTo(R.string.audio_streams_detail_page_title);
|
||||
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
|
||||
.isEqualTo(AUDIO_STREAM_MAIN);
|
||||
|
||||
Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
assertThat(bundle).isNotNull();
|
||||
assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG))
|
||||
.isEqualTo(BROADCAST_TITLE);
|
||||
assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG))
|
||||
.isEqualTo(BROADCAST_ID);
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
||||
@@ -28,10 +28,16 @@ import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
||||
import android.app.AlertDialog;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -42,7 +48,9 @@ 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.ShadowAlertDialog;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
@@ -51,6 +59,10 @@ import org.robolectric.shadows.ShadowAlertDialog;
|
||||
})
|
||||
public class SyncedStateTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final String ENCRYPTED_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 BROADCAST_TITLE = "title";
|
||||
@Mock private AudioStreamsProgressCategoryController mMockController;
|
||||
@Mock private AudioStreamPreference mMockPreference;
|
||||
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
|
||||
@@ -105,18 +117,47 @@ public class SyncedStateTest {
|
||||
|
||||
@Test
|
||||
public void testGetOnClickListener_isEncrypted_passwordDialogShowing() {
|
||||
when(mMockPreference.getAudioStreamMetadata())
|
||||
.thenReturn(
|
||||
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
|
||||
ENCRYPTED_METADATA));
|
||||
when(mMockPreference.getContext()).thenReturn(mMockContext);
|
||||
when(mMockPreference.getTitle()).thenReturn(BROADCAST_TITLE);
|
||||
|
||||
Preference.OnPreferenceClickListener listener =
|
||||
mInstance.getOnClickListener(mMockController);
|
||||
when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
|
||||
when(mMockPreference.getContext()).thenReturn(mMockContext);
|
||||
when(mMockMetadata.isEncrypted()).thenReturn(true);
|
||||
assertThat(listener).isNotNull();
|
||||
|
||||
listener.onPreferenceClick(mMockPreference);
|
||||
shadowMainLooper().idle();
|
||||
|
||||
AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
|
||||
|
||||
assertThat(dialog).isNotNull();
|
||||
assertThat(dialog.isShowing()).isTrue();
|
||||
verify(mMockController, never()).handleSourceAddRequest(mMockPreference, mMockMetadata);
|
||||
|
||||
Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
|
||||
assertThat(neutralButton).isNotNull();
|
||||
assertThat(neutralButton.getText().toString())
|
||||
.isEqualTo(mMockContext.getString(android.R.string.cancel));
|
||||
|
||||
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||
assertThat(positiveButton).isNotNull();
|
||||
assertThat(positiveButton.getText().toString())
|
||||
.isEqualTo(
|
||||
mMockContext.getString(R.string.bluetooth_connect_access_dialog_positive));
|
||||
|
||||
positiveButton.callOnClick();
|
||||
ShadowLooper.idleMainLooper();
|
||||
verify(mMockController).handleSourceAddRequest(any(), any());
|
||||
|
||||
ShadowAlertDialog shadowDialog = Shadow.extract(dialog);
|
||||
TextView title = shadowDialog.getView().findViewById(R.id.broadcast_name_text);
|
||||
assertThat(title).isNotNull();
|
||||
assertThat(title.getText().toString()).isEqualTo(BROADCAST_TITLE);
|
||||
assertThat(shadowDialog.getTitle().toString())
|
||||
.isEqualTo(mMockContext.getString(R.string.find_broadcast_password_dialog_title));
|
||||
|
||||
dialog.cancel();
|
||||
}
|
||||
}
|
||||
|
@@ -16,22 +16,39 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static android.app.settings.SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT;
|
||||
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.AUDIO_STREAM_WAIT_FOR_SYNC_STATE_SUMMARY;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.WAIT_FOR_SYNC_TIMEOUT_MILLIS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
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;
|
||||
@@ -43,15 +60,18 @@ import java.util.concurrent.TimeUnit;
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class WaitForSyncStateTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
@Mock private AudioStreamPreference mMockPreference;
|
||||
@Mock private AudioStreamsProgressCategoryController mMockController;
|
||||
@Mock private AudioStreamsHelper mMockHelper;
|
||||
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
private WaitForSyncState mInstance;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mInstance = WaitForSyncState.getInstance();
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mInstance = new WaitForSyncState();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,12 +113,49 @@ public class WaitForSyncStateTest {
|
||||
.thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC);
|
||||
when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(1);
|
||||
when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
|
||||
when(mMockPreference.getContext()).thenReturn(mContext);
|
||||
when(mMockPreference.getSourceOriginForLogging())
|
||||
.thenReturn(SourceOriginForLogging.UNKNOWN);
|
||||
.thenReturn(SourceOriginForLogging.BROADCAST_SEARCH);
|
||||
when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class));
|
||||
|
||||
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
|
||||
ShadowLooper.idleMainLooper(WAIT_FOR_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
||||
verify(mMockController).handleSourceLost(1);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
eq(mContext),
|
||||
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_WAIT_FOR_SYNC_TIMEOUT),
|
||||
eq(SourceOriginForLogging.BROADCAST_SEARCH.ordinal()));
|
||||
verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available);
|
||||
verify(mContext).getString(R.string.audio_streams_is_not_playing);
|
||||
verify(mContext).getString(R.string.audio_streams_dialog_close);
|
||||
verify(mContext).getString(R.string.audio_streams_dialog_retry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchQrCodeScanFragment() {
|
||||
// mContext is not an Activity context, calling startActivity() from outside of an Activity
|
||||
// context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
|
||||
// AndroidRuntimeException.
|
||||
Context activityContext = mock(Context.class);
|
||||
AudioStreamsDashboardFragment fragment = mock(AudioStreamsDashboardFragment.class);
|
||||
mInstance.launchQrCodeScanFragment(activityContext, fragment);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<Integer> requestCodeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(fragment)
|
||||
.startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture());
|
||||
|
||||
Intent intent = intentCaptor.getValue();
|
||||
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(AudioStreamsQrCodeScanFragment.class.getName());
|
||||
assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
|
||||
.isEqualTo(R.string.audio_streams_main_page_scan_qr_code_title);
|
||||
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
|
||||
.isEqualTo(DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT);
|
||||
|
||||
int requestCode = requestCodeCaptor.getValue();
|
||||
assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE);
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ import org.robolectric.annotation.Resetter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = false)
|
||||
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = true)
|
||||
public class ShadowAudioStreamsHelper {
|
||||
private static AudioStreamsHelper sMockHelper;
|
||||
@Nullable private static CachedBluetoothDevice sCachedBluetoothDevice;
|
||||
|
Reference in New Issue
Block a user