Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit de7ab402 authored by Yiyi Shen's avatar Yiyi Shen Committed by Android (Google) Code Review
Browse files

Merge "[Audiosharing] Show retry dialog when error occurs in start sharing" into main

parents e91214d3 302b26ff
Loading
Loading
Loading
Loading
+114 −26
Original line number Diff line number Diff line
@@ -41,7 +41,9 @@ import android.widget.CompoundButton.OnCheckedChangeListener;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -69,7 +71,6 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -113,14 +114,21 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
    private final Executor mExecutor;
    private final MetricsFeatureProvider mMetricsFeatureProvider;
    private final OnAudioSharingStateChangedListener mListener;
    @VisibleForTesting IntentFilter mIntentFilter;
    private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
    @Nullable private AudioSharingDeviceItem mTargetActiveItem;
    private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
    @VisibleForTesting IntentFilter mIntentFilter;
    private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
    private AtomicInteger mIntentHandleStage =
            new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal());
    // The sinks in adding source process. We show the progress dialog based on this list.
    private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
    // The primary/active sinks in adding source process.
    // To avoid users advance to share then pair flow before the primary/active sinks successfully
    // join the audio sharing, we will wait for the process complete for this list of sinks and then
    // popup audio sharing dialog with options to pair new device.
    private CopyOnWriteArrayList<BluetoothDevice> mSinksToWaitFor = new CopyOnWriteArrayList<>();
    private AtomicBoolean mStoppingSharing = new AtomicBoolean(false);

    @VisibleForTesting
    BroadcastReceiver mReceiver =
@@ -153,6 +161,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                public void onBroadcastStartFailed(int reason) {
                    Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
                    updateSwitch();
                    showRetryDialog();
                    mMetricsFeatureProvider.action(
                            mContext,
                            SettingsEnums.ACTION_AUDIO_SHARING_START_FAILED,
@@ -178,7 +187,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                                    + reason
                                    + ", broadcastId = "
                                    + broadcastId);
                    mStoppingSharing.compareAndSet(true, false);
                    updateSwitch();
                    AudioSharingUtils.postOnMainThread(mContext,
                            () -> dismissStaleDialogsOtherThanRetryDialog());
                    AudioSharingUtils.toastMessage(
                            mContext,
                            mContext.getString(R.string.audio_sharing_sharing_stopped_label));
@@ -219,7 +231,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                                TAG,
                                "Skip handleOnBroadcastReady: null assistant or "
                                        + "sink has active local source.");
                        cleanUp();
                        cleanUpStatesForStartSharing();
                        return;
                    }
                    handleOnBroadcastReady();
@@ -264,17 +276,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                                    + source
                                    + ", reason = "
                                    + reason);
                    if (mSinksInAdding.contains(sink)) {
                        stopAudioSharing();
                        showRetryDialog();
                        mMetricsFeatureProvider.action(
                                mContext,
                                SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED,
                                SettingsEnums.AUDIO_SHARING_SETTINGS);
                    AudioSharingUtils.toastMessage(
                            mContext,
                            String.format(
                                    Locale.US,
                                    "Fail to add source to %s reason %d",
                                    sink.getAddress(),
                                    reason));
                    }
                }

                @Override
@@ -298,6 +307,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                        @NonNull BluetoothDevice sink,
                        int sourceId,
                        @NonNull BluetoothLeBroadcastReceiveState state) {
                    if (mStoppingSharing.get()) {
                        Log.d(TAG, "Skip onReceiveStateChanged, stopping broadcast");
                        return;
                    }
                    if (BluetoothUtils.isConnected(state)) {
                        if (mSinksInAdding.contains(sink)) {
                            mSinksInAdding.remove(sink);
@@ -305,6 +318,22 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                        dismissLoadingStateDialogIfNeeded();
                        Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
                                + ", remaining sinks = " + mSinksInAdding);
                        if (mSinksToWaitFor.contains(sink)) {
                            mSinksToWaitFor.remove(sink);
                            if (mSinksToWaitFor.isEmpty()) {
                                // To avoid users advance to share then pair flow before the
                                // primary/active sinks successfully join the audio sharing,
                                // popup dialog till adding source complete for mSinksToWaitFor.
                                Pair<Integer, Object>[] eventData =
                                        AudioSharingUtils.buildAudioSharingDialogEventData(
                                                SettingsEnums.AUDIO_SHARING_SETTINGS,
                                                SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
                                                /* userTriggered= */ false,
                                                /* deviceCountInSharing= */ 1,
                                                /* candidateDeviceCount= */ 0);
                                showAudioSharingDialog(eventData);
                            }
                        }
                    }
                }
            };
@@ -411,6 +440,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                return;
            }
            stopAudioSharing();
            mMetricsFeatureProvider.action(
                    mContext, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_OFF);
        }
    }

@@ -553,9 +584,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController

    private void stopAudioSharing() {
        if (mBroadcast != null) {
            mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
            mMetricsFeatureProvider.action(
                    mContext, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_OFF);
            int broadcastId = mBroadcast.getLatestBroadcastId();
            if (broadcastId != -1) {
                mBroadcast.stopBroadcast(broadcastId);
                mStoppingSharing.compareAndSet(false, true);
                mSinksInAdding.clear();
                mSinksToWaitFor.clear();
            }
            cleanUpStatesForStartSharing();
        }
    }

@@ -617,11 +653,22 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                        /* userTriggered= */ false,
                        /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
                        /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
        // Auto add primary/active sinks w/o user interactions.
        if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) {
            Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
            addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
            // To avoid users advance to share then pair flow before the primary/active sinks
            // successfully join the audio sharing, save the primary/active sinks in mSinksToWaitFor
            // and popup dialog till adding source complete for these sinks.
            if (mDeviceItemsForSharing.isEmpty()) {
                mSinksToWaitFor.clear();
                mSinksToWaitFor.addAll(targetActiveSinks);
            }
            mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
            mTargetActiveItem = null;
            // When audio sharing page is brought up by intent with EXTRA_START_LE_AUDIO_SHARING
            // == true, plus there is one active lea headset and one connected lea headset, we
            // should auto add these sinks without user interactions.
            if (mIntentHandleStage.compareAndSet(
                    StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
                    StartIntentHandleStage.HANDLED.ordinal())
@@ -631,31 +678,42 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
                        target.getGroupId(), ImmutableList.of());
                addSourceToTargetSinks(targetSinks, target.getName());
                cleanUp();
                cleanUpStatesForStartSharing();
                // TODO: Add metric for auto add by intent
                return;
            }
        }
        // Still mark intent as handled if early returned due to preconditions not met
        mIntentHandleStage.compareAndSet(
                StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
                StartIntentHandleStage.HANDLED.ordinal());
        if (mFragment == null) {
            Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
            // Clean up states before early return.
            dismissLoadingStateDialogIfNeeded();
            cleanUp();
            cleanUpStatesForStartSharing();
            return;
        }
        showDialog(eventData);
        // To avoid users advance to share then pair flow before the primary/active sinks
        // successfully join the audio sharing, popup dialog till adding source complete for
        // mSinksToWaitFor.
        if (mSinksToWaitFor.isEmpty() && !mStoppingSharing.get()) {
            showAudioSharingDialog(eventData);
        }
    }

    private void showDialog(Pair<Integer, Object>[] eventData) {
    private void showAudioSharingDialog(Pair<Integer, Object>[] eventData) {
        if (!BluetoothUtils.isBroadcasting(mBtManager)) {
            Log.d(TAG, "Skip showAudioSharingDialog, broadcast is stopped");
            return;
        }
        AudioSharingDialogFragment.DialogEventListener listener =
                new AudioSharingDialogFragment.DialogEventListener() {
                    @Override
                    public void onPositiveClick() {
                        // Could go to other pages, dismiss the loading dialog.
                        dismissLoadingStateDialogIfNeeded();
                        cleanUp();
                        cleanUpStatesForStartSharing();
                    }

                    @Override
@@ -663,14 +721,14 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                        List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
                                item.getGroupId(), ImmutableList.of());
                        addSourceToTargetSinks(targetSinks, item.getName());
                        cleanUp();
                        cleanUpStatesForStartSharing();
                    }

                    @Override
                    public void onCancelClick() {
                        // Could go to other pages, dismiss the loading dialog.
                        dismissLoadingStateDialogIfNeeded();
                        cleanUp();
                        cleanUpStatesForStartSharing();
                    }
                };
        AudioSharingUtils.postOnMainThread(
@@ -684,6 +742,36 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
                });
    }

    private void showRetryDialog() {
        AudioSharingUtils.postOnMainThread(mContext,
                () -> {
                    // Remove all opening dialogs before show retry dialog
                    dismissStaleDialogsOtherThanRetryDialog();
                    AudioSharingRetryDialogFragment.show(mFragment);
                });
    }

    @UiThread
    private void dismissStaleDialogsOtherThanRetryDialog() {
        List<Fragment> fragments = new ArrayList<Fragment>();
        try {
            if (mFragment != null) {
                fragments =
                        mFragment.getChildFragmentManager().getFragments();
            }
        } catch (Exception e) {
            Log.e(TAG, "Fail to dismiss stale dialogs: " + e.getMessage());
        }
        for (Fragment fragment : fragments) {
            if (fragment != null && fragment instanceof DialogFragment
                    && !(fragment instanceof AudioSharingRetryDialogFragment)
                    && ((DialogFragment) fragment).getDialog() != null) {
                Log.d(TAG, "Remove stale dialog = " + fragment.getTag());
                ((DialogFragment) fragment).dismiss();
            }
        }
    }

    private static final class MainSwitchAccessibilityDelegate extends View.AccessibilityDelegate {
        @Override
        public boolean onRequestSendAccessibilityEvent(
@@ -742,10 +830,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
    private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
            @NonNull String sinkName) {
        mSinksInAdding.addAll(targetActiveSinks);
        AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
        // TODO: move to res once finalized
        String loadingMessage = "Sharing with " + sinkName + "...";
        showLoadingStateDialog(loadingMessage);
        AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
    }

    private void showLoadingStateDialog(@NonNull String loadingMessage) {
@@ -760,7 +848,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
        }
    }

    private void cleanUp() {
    private void cleanUpStatesForStartSharing() {
        mGroupedConnectedDevices.clear();
        mDeviceItemsForSharing.clear();
    }
+116 −16

File changed.

Preview size limit exceeded, changes collapsed.