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:
Chelsea Hao
2024-06-24 08:02:55 +00:00
committed by Android (Google) Code Review
33 changed files with 2756 additions and 98 deletions

View File

@@ -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();

View File

@@ -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(

View File

@@ -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!");
}

View File

@@ -36,7 +36,8 @@ class AddSourceWaitForResponseState extends AudioStreamStateHandler {
@Nullable private static AddSourceWaitForResponseState sInstance = null;
private AddSourceWaitForResponseState() {}
@VisibleForTesting
AddSourceWaitForResponseState() {}
static AddSourceWaitForResponseState getInstance() {
if (sInstance == null) {

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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())