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

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

Merge "[Audiosharing] Add dynamic stream for audio sharing" into main

parents 59f4204a 930bf49b
Loading
Loading
Loading
Loading
+74 −5
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.volume;

import static android.media.AudioManager.RINGER_MODE_NORMAL;

import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;

import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -59,6 +61,8 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Observer;

import com.android.internal.annotations.GuardedBy;
@@ -76,6 +80,8 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;

import dalvik.annotation.optimization.NeverCompile;

@@ -102,7 +108,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
    private static final int DYNAMIC_STREAM_START_INDEX = 100;
    // We only need one dynamic stream for broadcast because at most two headsets are allowed
    // to join local broadcast in current stage.
    // It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
    // streams defined in AudioSystem for now and audio team is in the middle of restructure,
    // no new default stream is preferred.
    @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99;
    private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
    private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
            new AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -145,6 +157,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
    private final State mState = new State();
    protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
    private final VibratorHelper mVibrator;
    private final AudioSharingInteractor mAudioSharingInteractor;
    private final JavaAdapter mJavaAdapter;
    private final boolean mHasVibrator;
    private boolean mShowA11yStream;
    private boolean mShowVolumeDialog;
@@ -188,7 +202,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
            KeyguardManager keyguardManager,
            ActivityManager activityManager,
            UserTracker userTracker,
            DumpManager dumpManager
            DumpManager dumpManager,
            AudioSharingInteractor audioSharingInteractor,
            JavaAdapter javaAdapter
    ) {
        mContext = context.getApplicationContext();
        mPackageManager = packageManager;
@@ -200,6 +216,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
        mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
        mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
        mAudioSharingInteractor = audioSharingInteractor;
        mJavaAdapter = javaAdapter;
        mAudio = audioManager;
        mNoMan = notificationManager;
        mObserver = new SettingObserver(mWorker);
@@ -272,6 +290,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        } catch (SecurityException e) {
            Log.w(TAG, "No access to media sessions", e);
        }
        if (volumeDialogAudioSharingFix()) {
            Slog.d(TAG, "Start collect volume changes in audio sharing");
            mJavaAdapter.alwaysCollectFlow(
                    mAudioSharingInteractor.getVolume(),
                    this::handleAudioSharingStreamVolumeChanges);
        }
    }

    public void setVolumePolicy(VolumePolicy policy) {
@@ -545,7 +569,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        mState.activeStream = activeStream;
        Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
        if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
        final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
        final int s =
                activeStream
                                < (volumeDialogAudioSharingFix()
                                        ? DYNAMIC_STREAM_BROADCAST
                                        : DYNAMIC_STREAM_REMOTE_START_INDEX)
                        ? activeStream
                        : -1;
        if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
        mAudio.forceVolumeControlStream(s);
        return true;
@@ -726,7 +756,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa

    private void onSetStreamVolumeW(int stream, int level) {
        if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
        if (stream >= DYNAMIC_STREAM_START_INDEX) {
        if (volumeDialogAudioSharingFix() && stream == DYNAMIC_STREAM_BROADCAST) {
            Slog.d(TAG, "onSetStreamVolumeW set broadcast stream level = " + level);
            mAudioSharingInteractor.setStreamVolume(level);
            return;
        }
        if (stream >= DYNAMIC_STREAM_REMOTE_START_INDEX) {
            mMediaSessionsCallbacksW.setStreamVolume(stream, level);
            return;
        }
@@ -758,6 +793,40 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        DndTile.setVisible(mContext, true);
    }

    void handleAudioSharingStreamVolumeChanges(@Nullable Integer volume) {
        if (volume == null) {
            if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
                mState.states.remove(DYNAMIC_STREAM_BROADCAST);
                Slog.d(TAG, "Remove audio sharing stream");
                mCallbacks.onStateChanged(mState);
            }
        } else {
            if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
                StreamState ss = mState.states.get(DYNAMIC_STREAM_BROADCAST);
                if (ss.level != volume) {
                    ss.level = volume;
                    Slog.d(TAG, "updateState, audio sharing stream volume = " + volume);
                    mCallbacks.onStateChanged(mState);
                }
            } else {
                StreamState ss = streamStateW(DYNAMIC_STREAM_BROADCAST);
                ss.dynamic = true;
                ss.levelMin = mAudioSharingInteractor.getVolumeMin();
                ss.levelMax = mAudioSharingInteractor.getVolumeMax();
                if (ss.level != volume) {
                    ss.level = volume;
                }
                String label = mContext.getString(R.string.audio_sharing_description);
                if (!Objects.equals(ss.remoteLabel, label)) {
                    ss.name = -1;
                    ss.remoteLabel = label;
                }
                Slog.d(TAG, "updateState, new audio sharing stream volume = " + volume);
                mCallbacks.onStateChanged(mState);
            }
        }
    }

    private final class VC extends IVolumeController.Stub {
        private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";

@@ -1256,7 +1325,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
    protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
        private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();

        private int mNextStream = DYNAMIC_STREAM_START_INDEX;
        private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
        private final boolean mVolumeAdjustmentForRemoteGroupSessions;

        public MediaSessionsCallbacks(Context context) {
+28 −4
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -1678,6 +1679,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
                return true;
            }

            // Always show the stream for audio sharing if it exists.
            if (volumeDialogAudioSharingFix()
                    && row.ss != null
                    && mContext.getString(R.string.audio_sharing_description)
                            .equals(row.ss.remoteLabel)) {
                return true;
            }

            if (row.defaultStream) {
                return activeRow.stream == STREAM_RING
                        || activeRow.stream == STREAM_ALARM
@@ -1880,10 +1889,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
            if (!ss.dynamic) continue;
            mDynamic.put(stream, true);
            if (findRow(stream) == null) {
                addRow(stream,
                if (volumeDialogAudioSharingFix()
                        && mContext.getString(R.string.audio_sharing_description)
                                .equals(ss.remoteLabel)) {
                    addRow(
                            stream,
                            R.drawable.ic_volume_media,
                            R.drawable.ic_volume_media_mute,
                            true,
                            false,
                            true);
                } else {
                    addRow(
                            stream,
                            com.android.settingslib.R.drawable.ic_volume_remote,
                            com.android.settingslib.R.drawable.ic_volume_remote_mute,
                        true, false, true);
                            true,
                            false,
                            true);
                }
            }
        }

+88 −10
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.volume;

import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,16 +39,19 @@ import android.media.IAudioService;
import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Process;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.settingslib.flags.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
@@ -54,7 +59,9 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;

import org.junit.Before;
import org.junit.Test;
@@ -63,6 +70,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Objects;
import java.util.concurrent.Executor;

@RunWith(AndroidJUnit4.class)
@@ -104,6 +112,10 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
    private UserTracker mUserTracker;
    @Mock
    private DumpManager mDumpManager;
    @Mock
    private AudioSharingInteractor mAudioSharingInteractor;
    @Mock
    private JavaAdapter mJavaAdapter;


    @Before
@@ -124,11 +136,26 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {

        mCallback = mock(VolumeDialogControllerImpl.C.class);
        mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
        mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
                mActivityManager, mUserTracker, mDumpManager, mCallback);
        mVolumeController =
                new TestableVolumeDialogControllerImpl(
                        mContext,
                        mBroadcastDispatcher,
                        mRingerModeTracker,
                        mThreadFactory,
                        mAudioManager,
                        mNotificationManager,
                        mVibrator,
                        mIAudioService,
                        mAccessibilityManager,
                        mPackageManager,
                        mWakefullnessLifcycle,
                        mKeyguardManager,
                        mActivityManager,
                        mUserTracker,
                        mDumpManager,
                        mCallback,
                        mAudioSharingInteractor,
                        mJavaAdapter);
        mVolumeController.setEnableDialogs(true, true);
    }

@@ -224,6 +251,41 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
        verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
    }

    @Test
    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
    public void handleAudioSharingStreamVolumeChanges_updateState() {
        ArgumentCaptor<VolumeDialogController.State> stateCaptor =
                ArgumentCaptor.forClass(VolumeDialogController.State.class);
        int broadcastStream = VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;

        mVolumeController.handleAudioSharingStreamVolumeChanges(100);

        verify(mCallback).onStateChanged(stateCaptor.capture());
        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
        assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(100);

        mVolumeController.handleAudioSharingStreamVolumeChanges(200);

        verify(mCallback, times(2)).onStateChanged(stateCaptor.capture());
        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
        assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(200);

        mVolumeController.handleAudioSharingStreamVolumeChanges(null);

        verify(mCallback, times(3)).onStateChanged(stateCaptor.capture());
        assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isFalse();
    }

    @Test
    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
    public void testSetStreamVolume_setSecondaryDeviceVolume() {
        mVolumeController.setStreamVolume(
                VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST, /* level= */ 100);
        Objects.requireNonNull(TestableLooper.get(this)).processAllMessages();

        verify(mAudioSharingInteractor).setStreamVolume(100);
    }

    static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
        private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;

@@ -243,11 +305,27 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
                ActivityManager activityManager,
                UserTracker userTracker,
                DumpManager dumpManager,
                C callback) {
            super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                    notificationManager, optionalVibrator, iAudioService, accessibilityManager,
                    packageManager, wakefulnessLifecycle, keyguardManager,
                    activityManager, userTracker, dumpManager);
                C callback,
                AudioSharingInteractor audioSharingInteractor,
                JavaAdapter javaAdapter) {
            super(
                    context,
                    broadcastDispatcher,
                    ringerModeTracker,
                    theadFactory,
                    audioManager,
                    notificationManager,
                    optionalVibrator,
                    iAudioService,
                    accessibilityManager,
                    packageManager,
                    wakefulnessLifecycle,
                    keyguardManager,
                    activityManager,
                    userTracker,
                    dumpManager,
                    audioSharingInteractor,
                    javaAdapter);
            mCallbacks = callback;

            ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
+34 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;

import static junit.framework.Assert.assertEquals;
@@ -72,6 +73,7 @@ import androidx.test.filters.SmallTest;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.settingslib.flags.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
@@ -794,6 +796,38 @@ public class VolumeDialogImplTest extends SysuiTestCase {
        verify(mVolumeDialogInteractor, atLeastOnce()).onDialogDismissed(); // dismiss by timeout
    }

    @Test
    @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
    public void testDynamicStreamForBroadcast_createRow() {
        State state = createShellState();
        VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
        ss.dynamic = true;
        ss.levelMin = 0;
        ss.levelMax = 255;
        ss.level = 20;
        ss.name = -1;
        ss.remoteLabel = mContext.getString(R.string.audio_sharing_description);
        state.states.append(DYNAMIC_STREAM_BROADCAST, ss);

        mDialog.onStateChangedH(state);
        mTestableLooper.processAllMessages();

        ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows);
        assumeNotNull(volumeDialogRows);
        View broadcastRow = null;
        final int rowCount = volumeDialogRows.getChildCount();
        // we don't make assumptions about the position of the dnd row
        for (int i = 0; i < rowCount; i++) {
            View volumeRow = volumeDialogRows.getChildAt(i);
            if (volumeRow.getId() == DYNAMIC_STREAM_BROADCAST) {
                broadcastRow = volumeRow;
                break;
            }
        }
        assertNotNull(broadcastRow);
        assertEquals(broadcastRow.getVisibility(), View.VISIBLE);
    }

    /**
     * @return true if at least one volume row has the DND icon
     */