Merge "[Audiosharing] Refine share then pair flow" into main

This commit is contained in:
Yiyi Shen
2024-09-19 13:29:37 +00:00
committed by Android (Google) Code Review
11 changed files with 672 additions and 75 deletions

View File

@@ -16,10 +16,18 @@
package com.android.settings.connecteddevice.audiosharing;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
@@ -27,16 +35,21 @@ import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.utils.ThreadUtils;
public class AudioSharingDashboardFragment extends DashboardFragment
implements AudioSharingSwitchBarController.OnAudioSharingStateChangedListener {
private static final String TAG = "AudioSharingDashboardFrag";
public static final int SHARE_THEN_PAIR_REQUEST_CODE = 1002;
SettingsMainSwitchBar mMainSwitchBar;
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
private AudioStreamsCategoryController mAudioStreamsCategoryController;
private AudioSharingSwitchBarController mAudioSharingSwitchBarController;
public AudioSharingDashboardFragment() {
super();
@@ -84,13 +97,38 @@ public class AudioSharingDashboardFragment extends DashboardFragment
final SettingsActivity activity = (SettingsActivity) getActivity();
mMainSwitchBar = activity.getSwitchBar();
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
AudioSharingSwitchBarController switchBarController =
mAudioSharingSwitchBarController =
new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
switchBarController.init(this);
getSettingsLifecycle().addObserver(switchBarController);
mAudioSharingSwitchBarController.init(this);
getSettingsLifecycle().addObserver(mAudioSharingSwitchBarController);
mMainSwitchBar.show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (!BluetoothUtils.isAudioSharingEnabled()) return;
// In share then pair flow, after users be routed to pair new device page and successfully
// pair and connect an LEA headset, the pair fragment will be finished with RESULT_OK
// and EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, pass the BT device to switch bar controller,
// which is responsible for adding source to the device with loading indicator.
if (requestCode == SHARE_THEN_PAIR_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
BluetoothDevice btDevice =
data != null
? data.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
BluetoothDevice.class)
: null;
Log.d(TAG, "onActivityResult: RESULT_OK with device = " + btDevice);
if (btDevice != null) {
var unused = ThreadUtils.postOnBackgroundThread(
() -> mAudioSharingSwitchBarController.handleAutoAddSourceAfterPair(
btDevice));
}
}
}
}
@Override
public void onAudioSharingStateChanged() {
updateVisibilityForAttachedPreferences();
@@ -107,11 +145,13 @@ public class AudioSharingDashboardFragment extends DashboardFragment
AudioSharingDeviceVolumeGroupController volumeGroupController,
AudioSharingCallAudioPreferenceController callAudioController,
AudioSharingPlaySoundPreferenceController playSoundController,
AudioStreamsCategoryController streamsCategoryController) {
AudioStreamsCategoryController streamsCategoryController,
AudioSharingSwitchBarController switchBarController) {
mAudioSharingDeviceVolumeGroupController = volumeGroupController;
mAudioSharingCallAudioPreferenceController = callAudioController;
mAudioSharingPlaySoundPreferenceController = playSoundController;
mAudioStreamsCategoryController = streamsCategoryController;
mAudioSharingSwitchBarController = switchBarController;
}
private void updateVisibilityForAttachedPreferences() {

View File

@@ -16,6 +16,9 @@
package com.android.settings.connecteddevice.audiosharing;
import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
@@ -48,19 +51,23 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
// The host creates an instance of this dialog fragment must implement this interface to receive
// event callbacks.
public interface DialogEventListener {
/** Called when users click the positive button in the dialog. */
default void onPositiveClick() {}
/**
* Called when users click the device item for sharing in the dialog.
*
* @param item The device item clicked.
*/
void onItemClick(AudioSharingDeviceItem item);
default void onItemClick(@NonNull AudioSharingDeviceItem item) {}
/** Called when users click the cancel button in the dialog. */
void onCancelClick();
default void onCancelClick() {}
}
@Nullable private static DialogEventListener sListener;
private static Pair<Integer, Object>[] sEventData = new Pair[0];
@Nullable private static Fragment sHost;
@Override
public int getMetricsCategory() {
@@ -70,10 +77,10 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
/**
* Display the {@link AudioSharingDialogFragment} dialog.
*
* @param host The Fragment this dialog will be hosted.
* @param host The Fragment this dialog will be hosted.
* @param deviceItems The connected device items eligible for audio sharing.
* @param listener The callback to handle the user action on this dialog.
* @param eventData The eventData to log with for dialog onClick events.
* @param listener The callback to handle the user action on this dialog.
* @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@@ -88,6 +95,7 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
Log.d(TAG, "Fail to show dialog: " + e.getMessage());
return;
}
sHost = host;
sListener = listener;
sEventData = eventData;
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
@@ -136,23 +144,33 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
.setCustomPositiveButton(
R.string.audio_sharing_pair_button_label,
v -> {
dismiss();
new SubSettingLauncher(getContext())
.setDestination(BluetoothPairingDetail.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
if (sListener != null) {
sListener.onPositiveClick();
}
logDialogPositiveBtnClick();
dismiss();
Bundle args = new Bundle();
args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, true);
SubSettingLauncher launcher =
new SubSettingLauncher(getContext())
.setDestination(
BluetoothPairingDetail.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args);
if (sHost != null) {
launcher.setResultListener(sHost, SHARE_THEN_PAIR_REQUEST_CODE);
}
launcher.launch();
})
.setCustomNegativeButton(
R.string.audio_sharing_qrcode_button_label,
v -> {
dismiss();
onCancelClick();
new SubSettingLauncher(getContext())
.setTitleRes(R.string.audio_streams_qr_code_page_title)
.setDestination(AudioStreamsQrCodeFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
logDialogNegativeBtnClick();
});
} else if (deviceItems.size() == 1) {
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
@@ -166,8 +184,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
v -> {
if (sListener != null) {
sListener.onItemClick(deviceItem);
logDialogPositiveBtnClick();
}
logDialogPositiveBtnClick();
dismiss();
})
.setCustomNegativeButton(
@@ -182,8 +200,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
(AudioSharingDeviceItem item) -> {
if (sListener != null) {
sListener.onItemClick(item);
logDialogPositiveBtnClick();
}
logDialogPositiveBtnClick();
dismiss();
},
AudioSharingDeviceAdapter.ActionType.SHARE))
@@ -196,8 +214,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
private void onCancelClick() {
if (sListener != null) {
sListener.onCancelClick();
logDialogNegativeBtnClick();
}
logDialogNegativeBtnClick();
dismiss();
}

View File

@@ -29,7 +29,6 @@ import androidx.fragment.app.FragmentManager;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingIncompatDlg";
@@ -59,7 +58,7 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
*
* @param host The Fragment this dialog will be hosted.
*/
public static void show(@Nullable Fragment host, @NonNull CachedBluetoothDevice cachedDevice,
public static void show(@Nullable Fragment host, @NonNull String deviceName,
@NonNull DialogEventListener listener) {
if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
final FragmentManager manager;
@@ -77,7 +76,7 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
}
Log.d(TAG, "Show up the incompatible device dialog.");
final Bundle bundle = new Bundle();
bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
bundle.putString(BUNDLE_KEY_DEVICE_NAME, deviceName);
AudioSharingIncompatibleDialogFragment dialogFrag =
new AudioSharingIncompatibleDialogFragment();
dialogFrag.setArguments(bundle);

View File

@@ -115,10 +115,6 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
@NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
mHandler = new Handler(Looper.getMainLooper());
mHandler.postDelayed(() -> {
Log.d(TAG, "Auto dismiss dialog after timeout");
dismiss();
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
Bundle args = requireArguments();
String message = args.getString(BUNDLE_KEY_MESSAGE, "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -132,6 +128,26 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
return dialog;
}
@Override
public void onStart() {
super.onStart();
if (mHandler != null) {
Log.d(TAG, "onStart, postTimeOut for auto dismiss");
mHandler.postDelayed(() -> {
Log.d(TAG, "Try to auto dismiss dialog after timeout");
try {
Dialog dialog = getDialog();
if (dialog != null) {
Log.d(TAG, "Dialog is not null, dismiss");
dismissAllowingStateLoss();
}
} catch (IllegalStateException e) {
Log.d(TAG, "Fail to dismiss: " + e.getMessage());
}
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
}
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);

View File

@@ -56,6 +56,7 @@ import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -78,9 +79,9 @@ import java.util.concurrent.atomic.AtomicInteger;
public class AudioSharingSwitchBarController extends BasePreferenceController
implements DefaultLifecycleObserver,
OnCheckedChangeListener,
LocalBluetoothProfileManager.ServiceListener,
BluetoothCallback {
OnCheckedChangeListener,
LocalBluetoothProfileManager.ServiceListener,
BluetoothCallback {
private static final String TAG = "AudioSharingSwitchCtlr";
private static final String PREF_KEY = "audio_sharing_main_switch";
@@ -464,6 +465,18 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
this.mFragment = fragment;
}
/** Handle auto add source to the just paired device in share then pair flow. */
public void handleAutoAddSourceAfterPair(@NonNull BluetoothDevice device) {
CachedBluetoothDeviceManager deviceManager =
mBtManager == null ? null : mBtManager.getCachedDeviceManager();
CachedBluetoothDevice cachedDevice =
deviceManager == null ? null : deviceManager.findDevice(device);
if (cachedDevice != null) {
Log.d(TAG, "handleAutoAddSourceAfterPair, device = " + device.getAnonymizedAddress());
addSourceToTargetSinks(ImmutableList.of(device), cachedDevice.getName());
}
}
/** Test only: set callback registration status in tests. */
@VisibleForTesting
void setCallbacksRegistered(boolean registered) {
@@ -610,8 +623,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
mTargetActiveItem = null;
if (mIntentHandleStage.compareAndSet(
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal())
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal())
&& mDeviceItemsForSharing.size() == 1) {
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
@@ -638,6 +651,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private void showDialog(Pair<Integer, Object>[] eventData) {
AudioSharingDialogFragment.DialogEventListener listener =
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onPositiveClick() {
// Could go to other pages, dismiss the loading dialog.
dismissLoadingStateDialogIfNeeded();
cleanUp();
}
@Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
@@ -648,6 +668,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
@Override
public void onCancelClick() {
// Could go to other pages, dismiss the loading dialog.
dismissLoadingStateDialogIfNeeded();
cleanUp();
}
@@ -669,8 +690,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
@NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& (event.getContentChangeTypes()
& AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
!= 0) {
& AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
!= 0) {
Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED");
return false;
}