Handle multi-user cases on Live Caption toggle in Volume rocker

Bug: 272141009
Test: atest VolumeDialogControllerImplTest
Change-Id: I15e7bc8eeff83c6a9d749eb2f0c538937884c613
This commit is contained in:
Abner Huang
2023-07-14 08:54:08 +00:00
parent 3393c82f6d
commit fe2eb6cdc7
5 changed files with 182 additions and 41 deletions

View File

@@ -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);
}

View File

@@ -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;
@@ -155,16 +158,16 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
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 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
@@ -209,17 +211,19 @@ 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
.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 @@ 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) {
@@ -362,8 +385,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
mShowVolumeDialog = volumeUi;
mShowSafetyWarning = safetyWarning;
mShowVolumeDialog = volumeUi;
mShowSafetyWarning = safetyWarning;
}
@Override
@@ -414,12 +437,38 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
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 @@ 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 {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.
*/