Handle multi-user cases on Live Caption toggle in Volume rocker
Bug: 272141009
Test: atest VolumeDialogControllerImplTest
Change-Id: I15e7bc8eeff83c6a9d749eb2f0c538937884c613
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index cf7d2c5..3d9645a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -58,9 +58,26 @@
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 @@
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);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d39a53d..9cc3cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -44,6 +44,7 @@
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.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.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -131,7 +134,7 @@
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;
@@ -155,16 +158,16 @@
private final WakefulnessLifecycle.Observer mWakefullnessLifecycleObserver =
new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedWakingUp() {
- mDeviceInteractive = true;
- }
+ @Override
+ public void onStartedWakingUp() {
+ mDeviceInteractive = true;
+ }
- @Override
- public void onFinishedGoingToSleep() {
- mDeviceInteractive = false;
- }
- };
+ @Override
+ public void onFinishedGoingToSleep() {
+ mDeviceInteractive = false;
+ }
+ };
@Inject
public VolumeDialogControllerImpl(
@@ -179,7 +182,6 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
@@ -209,17 +211,19 @@
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
.isAccessibilityVolumeStreamActive();
mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
- VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
- VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
mWakefulnessLifecycle.addObserver(mWakefullnessLifecycleObserver);
}
@@ -316,12 +320,31 @@
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) {
@@ -362,8 +385,8 @@
}
public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
- mShowVolumeDialog = volumeUi;
- mShowSafetyWarning = safetyWarning;
+ mShowVolumeDialog = volumeUi;
+ mShowSafetyWarning = safetyWarning;
}
@Override
@@ -414,12 +437,38 @@
}
private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
- mCallbacks.onShowCsdWarning(csdWarning, durationMs);
+ mCallbacks.onShowCsdWarning(csdWarning, durationMs);
}
private void onGetCaptionsComponentStateW(boolean fromTooltip) {
- mCallbacks.onCaptionComponentStateChanged(
- mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ mCallbacks.onCaptionComponentStateChanged(
+ 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 @@
* 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 @@
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 @@
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 @@
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 {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 6219e4d..aafa16f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1333,21 +1333,30 @@
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 @@
} else {
updateRowsH(activeRow);
}
-
}
@Override
@@ -2371,6 +2379,11 @@
Boolean isComponentEnabled, Boolean fromTooltip) {
updateODICaptionsH(isComponentEnabled, fromTooltip);
}
+
+ @Override
+ public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
+ updateCaptionsEnabledH(isEnabled, checkForSwitchState);
+ }
};
@VisibleForTesting void onPostureChanged(int posture) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 0663004..69d7586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -40,7 +40,6 @@
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.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -96,8 +97,6 @@
@Mock
private WakefulnessLifecycle mWakefullnessLifcycle;
@Mock
- private CaptioningManager mCaptioningManager;
- @Mock
private KeyguardManager mKeyguardManager;
@Mock
private ActivityManager mActivityManager;
@@ -117,6 +116,7 @@
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 @@
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 @@
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 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
@@ -242,7 +246,7 @@
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
- packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
+ packageManager, wakefulnessLifecycle, keyguardManager,
activityManager, userTracker, dumpManager);
mCallbacks = callback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index fa18e57..2828eb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -31,6 +31,7 @@
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 @@
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ CaptionsToggleImageButton mODICaptionsIcon;
+
private TestableLooper mTestableLooper;
private ConfigurationController mConfigurationController;
private int mOriginalOrientation;
@@ -180,6 +183,7 @@
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 @@
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.
*/