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

Commit c1243b19 authored by Abner Huang's avatar Abner Huang Committed by Android (Google) Code Review
Browse files

Merge "Handle multi-user cases on Live Caption toggle in Volume rocker" into udc-qpr-dev

parents ed5b3bdc fe2eb6cd
Loading
Loading
Loading
Loading
+35 −3
Original line number Diff line number Diff line
@@ -58,9 +58,26 @@ public interface VolumeDialogController {
    void userActivity();
    void getState();

    boolean areCaptionsEnabled();
    void setCaptionsEnabled(boolean isEnabled);
    /**
     * Get Captions enabled state
     *
     * @param checkForSwitchState set true when we'd like to switch captions enabled state after
     *                            getting the latest captions state.
     */
    void getCaptionsEnabledState(boolean checkForSwitchState);

    /**
     * Set Captions enabled state
     *
     * @param enabled the captions enabled state we'd like to update.
     */
    void setCaptionsEnabledState(boolean enabled);

    /**
     * Get Captions component state
     *
     * @param fromTooltip if it's triggered from tooltip.
     */
    void getCaptionsComponentState(boolean fromTooltip);

    @ProvidesInterface(version = StreamState.VERSION)
@@ -192,7 +209,22 @@ public interface VolumeDialogController {
        void onScreenOff();
        void onShowSafetyWarning(int flags);
        void onAccessibilityModeChanged(Boolean showA11yStream);

        /**
         * Callback function for captions component state changed event
         *
         * @param isComponentEnabled the lateset captions component state.
         * @param fromTooltip if it's triggered from tooltip.
         */
        void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);

        /**
         * Callback function for captions enabled state changed event
         *
         * @param isEnabled the lateset captions enabled state.
         * @param checkBeforeSwitch intend to switch captions enabled state after the callback.
         */
        void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
        // requires version 2
        void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
    }
+90 −24
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -57,6 +58,7 @@ import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;

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

import com.android.internal.annotations.GuardedBy;
@@ -81,6 +83,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

import javax.inject.Inject;

@@ -131,7 +134,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
    private final Receiver mReceiver = new Receiver();
    private final RingerModeObservers mRingerModeObservers;
    private final MediaSessions mMediaSessions;
    private final CaptioningManager mCaptioningManager;
    private final AtomicReference<CaptioningManager> mCaptioningManager = new AtomicReference<>();
    private final KeyguardManager mKeyguardManager;
    private final ActivityManager mActivityManager;
    private final UserTracker mUserTracker;
@@ -179,7 +182,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
            AccessibilityManager accessibilityManager,
            PackageManager packageManager,
            WakefulnessLifecycle wakefulnessLifecycle,
            CaptioningManager captioningManager,
            KeyguardManager keyguardManager,
            ActivityManager activityManager,
            UserTracker userTracker,
@@ -209,10 +211,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        mVibrator = vibrator;
        mHasVibrator = mVibrator.hasVibrator();
        mAudioService = iAudioService;
        mCaptioningManager = captioningManager;
        mKeyguardManager = keyguardManager;
        mActivityManager = activityManager;
        mUserTracker = userTracker;
        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mWorker));
        createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());

        dumpManager.registerDumpable("VolumeDialogControllerImpl", this);

        boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -316,12 +320,31 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        mWorker.sendEmptyMessage(W.GET_STATE);
    }

    public boolean areCaptionsEnabled() {
        return mCaptioningManager.isSystemAudioCaptioningEnabled();
    /**
     * We met issues about the wrong state of System Caption in multi-user mode.
     * It happened in the usage of CaptioningManager Service from SysUI process
     * that is a global system process of User 0.
     * Therefore, we have to add callback on UserTracker that allows us to get the Context of
     * active User and then get the corresponding CaptioningManager Service for further usages.
     */
    private final UserTracker.Callback mUserChangedCallback =
            new UserTracker.Callback() {
                @Override
                public void onUserChanged(int newUser, @NonNull Context userContext) {
                    createCaptioningManagerServiceByUserContext(userContext);
                }
            };

    private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
        mCaptioningManager.set(userContext.getSystemService(CaptioningManager.class));
    }

    public void setCaptionsEnabled(boolean isEnabled) {
        mCaptioningManager.setSystemAudioCaptioningEnabled(isEnabled);
    public void getCaptionsEnabledState(boolean checkForSwitchState) {
        mWorker.obtainMessage(W.GET_CAPTIONS_ENABLED_STATE, checkForSwitchState).sendToTarget();
    }

    public void setCaptionsEnabledState(boolean enabled) {
        mWorker.obtainMessage(W.SET_CAPTIONS_ENABLED_STATE, enabled).sendToTarget();
    }

    public void getCaptionsComponentState(boolean fromTooltip) {
@@ -418,8 +441,34 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
    }

    private void onGetCaptionsComponentStateW(boolean fromTooltip) {
        CaptioningManager captioningManager = mCaptioningManager.get();
        if (null != captioningManager) {
            mCallbacks.onCaptionComponentStateChanged(
                mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
                    captioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
        } else {
            Log.e(TAG, "onGetCaptionsComponentStateW(), null captioningManager");
        }
    }

    private void onGetCaptionsEnabledStateW(boolean checkForSwitchState) {
        CaptioningManager captioningManager = mCaptioningManager.get();
        if (null != captioningManager) {
            mCallbacks.onCaptionEnabledStateChanged(
                    captioningManager.isSystemAudioCaptioningEnabled(), checkForSwitchState);
        } else {
            Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
        }
    }

    private void onSetCaptionsEnabledStateW(boolean enabled) {
        CaptioningManager captioningManager = mCaptioningManager.get();
        if (null != captioningManager) {
            captioningManager.setSystemAudioCaptioningEnabled(enabled);
            mCallbacks.onCaptionEnabledStateChanged(
                    captioningManager.isSystemAudioCaptioningEnabled(), false);
        } else {
            Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
        }
    }

    private void onAccessibilityModeChanged(Boolean showA11yStream) {
@@ -719,7 +768,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
         * This method will never be called if the CSD (Computed Sound Dose) feature is
         * not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
         * the feature.
         * @param warning the type of warning to display, values are one of
         * @param csdWarning the type of warning to display, values are one of
         *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
         *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
         *        {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
@@ -798,6 +847,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
        private static final int ACCESSIBILITY_MODE_CHANGED = 15;
        private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
        private static final int SHOW_CSD_WARNING = 17;
        private static final int GET_CAPTIONS_ENABLED_STATE = 18;
        private static final int SET_CAPTIONS_ENABLED_STATE = 19;

        W(Looper looper) {
            super(looper);
@@ -825,6 +876,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
                case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
                    break;
                case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
                case GET_CAPTIONS_ENABLED_STATE:
                    onGetCaptionsEnabledStateW((Boolean) msg.obj); break;
                case SET_CAPTIONS_ENABLED_STATE:
                    onSetCaptionsEnabledStateW((Boolean) msg.obj); break;
            }
        }
    }
@@ -993,6 +1048,17 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
                                componentEnabled, fromTooltip));
            }
        }

        @Override
        public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch) {
            boolean captionsEnabled = isEnabled != null && isEnabled;
            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
                entry.getValue().post(
                        () -> entry.getKey().onCaptionEnabledStateChanged(
                                captionsEnabled, checkBeforeSwitch));
            }
        }

    }

    private final class RingerModeObservers {
+22 −9
Original line number Diff line number Diff line
@@ -1333,21 +1333,30 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,

        if (!isServiceComponentEnabled) return;

        updateCaptionsIcon();
        checkEnabledStateForCaptionsIconUpdate();
        if (fromTooltip) showCaptionsTooltip();
    }

    private void updateCaptionsIcon() {
        boolean captionsEnabled = mController.areCaptionsEnabled();
        if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) {
            mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled));
    private void updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState) {
        if (checkForSwitchState) {
            mController.setCaptionsEnabledState(!isCaptionsEnabled);
        } else {
            updateCaptionsIcon(isCaptionsEnabled);
        }
    }

    private void checkEnabledStateForCaptionsIconUpdate() {
        mController.getCaptionsEnabledState(false);
    }

    private void updateCaptionsIcon(boolean isCaptionsEnabled) {
        if (mODICaptionsIcon.getCaptionsEnabled() != isCaptionsEnabled) {
            mHandler.post(mODICaptionsIcon.setCaptionsEnabled(isCaptionsEnabled));
        }
    }

    private void onCaptionIconClicked() {
        boolean isEnabled = mController.areCaptionsEnabled();
        mController.setCaptionsEnabled(!isEnabled);
        updateCaptionsIcon();
        mController.getCaptionsEnabledState(true);
    }

    private void incrementManualToggleCount() {
@@ -2363,7 +2372,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
            } else {
                updateRowsH(activeRow);
            }

        }

        @Override
@@ -2371,6 +2379,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
                Boolean isComponentEnabled, Boolean fromTooltip) {
            updateODICaptionsH(isComponentEnabled, fromTooltip);
        }

        @Override
        public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
            updateCaptionsEnabledH(isEnabled, checkForSwitchState);
        }
    };

    @VisibleForTesting void onPostureChanged(int posture) {
+10 −6
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import android.os.Process;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;

import androidx.test.filters.SmallTest;

@@ -64,6 +63,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.concurrent.Executor;

@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -96,8 +97,6 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
    @Mock
    private WakefulnessLifecycle mWakefullnessLifcycle;
    @Mock
    private CaptioningManager mCaptioningManager;
    @Mock
    private KeyguardManager mKeyguardManager;
    @Mock
    private ActivityManager mActivityManager;
@@ -117,6 +116,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
        when(mRingerModeLiveData.getValue()).thenReturn(-1);
        when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
        when(mUserTracker.getUserContext()).thenReturn(mContext);
        // Enable group volume adjustments
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -127,7 +127,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
        mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
                mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
                mActivityManager, mUserTracker, mDumpManager, mCallback);
        mVolumeController.setEnableDialogs(true, true);
    }
@@ -219,6 +219,11 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
        verify(mRingerModeInternalLiveData).observeForever(any());
    }

    @Test
    public void testAddCallbackWithUserTracker() {
        verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
    }

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

@@ -234,7 +239,6 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
                AccessibilityManager accessibilityManager,
                PackageManager packageManager,
                WakefulnessLifecycle wakefulnessLifecycle,
                CaptioningManager captioningManager,
                KeyguardManager keyguardManager,
                ActivityManager activityManager,
                UserTracker userTracker,
@@ -242,7 +246,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
                C callback) {
            super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                    notificationManager, optionalVibrator, iAudioService, accessibilityManager,
                    packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
                    packageManager, wakefulnessLifecycle, keyguardManager,
                    activityManager, userTracker, dumpManager);
            mCallbacks = callback;

+26 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue;

import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -93,6 +94,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
    View mDrawerVibrate;
    View mDrawerMute;
    View mDrawerNormal;
    CaptionsToggleImageButton mODICaptionsIcon;

    private TestableLooper mTestableLooper;
    private ConfigurationController mConfigurationController;
    private int mOriginalOrientation;
@@ -180,6 +183,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
        mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
        mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
        mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
        mODICaptionsIcon = mDialog.getDialogView().findViewById(R.id.odi_captions_icon);

        Prefs.putInt(mContext,
                Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
@@ -688,6 +692,28 @@ public class VolumeDialogImplTest extends SysuiTestCase {
        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
    }

    @Test
    public void testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState() {
        ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
        verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();

        callbacks.onCaptionEnabledStateChanged(true, true);
        verify(mVolumeDialogController).setCaptionsEnabledState(eq(false));
    }

    @Test
    public void testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue() {
        ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
        verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();

        callbacks.onCaptionEnabledStateChanged(true, false);
        assertTrue(mODICaptionsIcon.getCaptionsEnabled());
    }

    /**
     * The content description should include ringer state, and the correct one.
     */