diff options
4 files changed, 412 insertions, 1 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableStateTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableStateTracker.kt new file mode 100644 index 000000000000..562f58576ddc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableStateTracker.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.disableflags + +import com.android.systemui.statusbar.CommandQueue + +/** + * Tracks the relevant DISABLE_* flags provided in [mask1] and [mask2] and sets [isDisabled] based + * on those masks. [callback] will be notified whenever [isDisabled] changes. + * + * Users are responsible for adding and removing this tracker from [CommandQueue] callbacks. + */ +class DisableStateTracker( + private val mask1: Int, + private val mask2: Int, + private val callback: Callback, +) : CommandQueue.Callbacks { + /** + * True if any of the bits in [mask1] or [mask2] are on for the current disable flags, and false + * otherwise. + */ + var isDisabled = false + private set(value) { + if (field == value) return + field = value + callback.onDisabledChanged() + } + + private var displayId: Int? = null + + /** Start tracking the disable flags and updating [isDisabled] accordingly. */ + fun startTracking(commandQueue: CommandQueue, displayId: Int) { + // A view will only have its displayId once it's attached to a window, so we can only + // provide the displayId when we start tracking. + this.displayId = displayId + commandQueue.addCallback(this) + } + + /** + * Stop tracking the disable flags. + * + * [isDisabled] will stay at the same value until we start tracking again. + */ + fun stopTracking(commandQueue: CommandQueue) { + this.displayId = null + commandQueue.removeCallback(this) + } + + override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) { + if (this.displayId == null || displayId != this.displayId) { + return + } + isDisabled = state1 and mask1 != 0 || state2 and mask2 != 0 + } + + /** Callback triggered whenever the value of [isDisabled] changes. */ + fun interface Callback { + fun onDisabledChanged() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index f06b346780da..ce2c9c244696 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; +import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; + import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import android.animation.Animator; @@ -43,8 +46,10 @@ import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.disableflags.DisableStateTracker; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.AnimatableProperty; @@ -108,6 +113,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final StatusBarUserSwitcherController mUserSwitcherController; private final StatusBarUserInfoTracker mStatusBarUserInfoTracker; private final SecureSettings mSecureSettings; + private final CommandQueue mCommandQueue; private final Executor mMainExecutor; private final Object mLock = new Object(); @@ -218,6 +224,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } }; + + private final DisableStateTracker mDisableStateTracker; + private final List<String> mBlockedIcons = new ArrayList<>(); private final int mNotificationsHeaderCollideDistance; @@ -269,6 +278,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat StatusBarUserSwitcherController userSwitcherController, StatusBarUserInfoTracker statusBarUserInfoTracker, SecureSettings secureSettings, + CommandQueue commandQueue, @Main Executor mainExecutor ) { super(view); @@ -292,6 +302,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mUserSwitcherController = userSwitcherController; mStatusBarUserInfoTracker = statusBarUserInfoTracker; mSecureSettings = secureSettings; + mCommandQueue = commandQueue; mMainExecutor = mainExecutor; mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); @@ -316,6 +327,12 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat !mFeatureController.isStatusBarUserSwitcherFeatureEnabled()); mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled)); mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r); + + mDisableStateTracker = new DisableStateTracker( + /* mask1= */ DISABLE_SYSTEM_INFO, + /* mask2= */ DISABLE2_SYSTEM_ICONS, + this::updateViewState + ); } @Override @@ -333,6 +350,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mUserInfoController.addCallback(mOnUserInfoChangedListener); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); + mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId()); if (mTintedIconManager == null) { mTintedIconManager = mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons)); @@ -357,6 +375,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mUserInfoController.removeCallback(mOnUserInfoChangedListener); mStatusBarStateController.removeCallback(mStatusBarStateListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); + mDisableStateTracker.stopTracking(mCommandQueue); mSecureSettings.unregisterContentObserver(mVolumeSettingObserver); if (mTintedIconManager != null) { mStatusBarIconController.removeIconGroup(mTintedIconManager); @@ -411,6 +430,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Animate the keyguard status bar in. */ public void animateKeyguardStatusBarIn() { + if (mDisableStateTracker.isDisabled()) { + // If our view is disabled, don't allow us to animate in. + return; + } mView.setVisibility(View.VISIBLE); mView.setAlpha(0f); ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); @@ -463,7 +486,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat boolean hideForBypass = mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace() || mDelayShowingKeyguardStatusBar; - int newVisibility = newAlpha != 0f && !mDozing && !hideForBypass + int newVisibility = + newAlpha != 0f + && !mDozing + && !hideForBypass + && !mDisableStateTracker.isDisabled() ? View.VISIBLE : View.INVISIBLE; updateViewState(newAlpha, newVisibility); @@ -473,6 +500,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * Updates the {@link KeyguardStatusBarView} state based on the provided values. */ public void updateViewState(float alpha, int visibility) { + if (mDisableStateTracker.isDisabled()) { + visibility = View.INVISIBLE; + } mView.setAlpha(alpha); mView.setVisibility(visibility); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt new file mode 100644 index 000000000000..215afb2143df --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.disableflags + +import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS +import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_EXPAND +import android.app.StatusBarManager.DISABLE_NAVIGATION +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_TICKER +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.CommandQueue +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +class DisableStateTrackerTest : SysuiTestCase() { + + private lateinit var underTest: DisableStateTracker + + @Mock private lateinit var commandQueue: CommandQueue + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun startTracking_commandQueueGetsCallback() { + underTest = DisableStateTracker(0, 0) { } + + underTest.startTracking(commandQueue, displayId = 0) + + verify(commandQueue).addCallback(underTest) + } + + @Test + fun stopTracking_commandQueueLosesCallback() { + underTest = DisableStateTracker(0, 0) { } + + underTest.stopTracking(commandQueue) + + verify(commandQueue).removeCallback(underTest) + } + + @Test + fun disable_hadNotStartedTracking_isDisabledFalse() { + underTest = DisableStateTracker(DISABLE_CLOCK, 0) { } + + underTest.disable(displayId = 0, state1 = DISABLE_CLOCK, state2 = 0, animate = false) + + assertThat(underTest.isDisabled).isFalse() + } + + @Test + fun disable_wrongDisplayId_isDisabledFalse() { + underTest = DisableStateTracker(DISABLE_CLOCK, 0) { } + underTest.startTracking(commandQueue, displayId = 15) + + underTest.disable(displayId = 20, state1 = DISABLE_CLOCK, state2 = 0, animate = false) + + assertThat(underTest.isDisabled).isFalse() + } + + @Test + fun disable_irrelevantFlagsUpdated_isDisabledFalse() { + underTest = DisableStateTracker(DISABLE_CLOCK, DISABLE2_GLOBAL_ACTIONS) { } + underTest.startTracking(commandQueue, DISPLAY_ID) + + underTest.disable( + DISPLAY_ID, state1 = DISABLE_EXPAND, state2 = DISABLE2_QUICK_SETTINGS, animate = false + ) + + assertThat(underTest.isDisabled).isFalse() + } + + @Test + fun disable_partOfMask1True_isDisabledTrue() { + underTest = DisableStateTracker( + mask1 = DISABLE_CLOCK or DISABLE_EXPAND or DISABLE_NAVIGATION, + mask2 = DISABLE2_GLOBAL_ACTIONS + ) { } + underTest.startTracking(commandQueue, DISPLAY_ID) + + underTest.disable(DISPLAY_ID, state1 = DISABLE_EXPAND, state2 = 0, animate = false) + + assertThat(underTest.isDisabled).isTrue() + } + + @Test + fun disable_partOfMask2True_isDisabledTrue() { + underTest = DisableStateTracker( + mask1 = DISABLE_CLOCK, + mask2 = DISABLE2_GLOBAL_ACTIONS or DISABLE2_SYSTEM_ICONS + ) { } + underTest.startTracking(commandQueue, DISPLAY_ID) + + underTest.disable(DISPLAY_ID, state1 = 0, state2 = DISABLE2_SYSTEM_ICONS, animate = false) + + assertThat(underTest.isDisabled).isTrue() + } + + @Test + fun disable_isDisabledChangesFromFalseToTrue_callbackNotified() { + var callbackCalled = false + + underTest = DisableStateTracker( + mask1 = DISABLE_CLOCK, + mask2 = DISABLE2_GLOBAL_ACTIONS + ) { callbackCalled = true } + underTest.startTracking(commandQueue, DISPLAY_ID) + + underTest.disable(DISPLAY_ID, state1 = DISABLE_CLOCK, state2 = 0, animate = false) + + assertThat(callbackCalled).isTrue() + } + + @Test + fun disable_isDisabledChangesFromTrueToFalse_callbackNotified() { + var callbackCalled: Boolean + + underTest = DisableStateTracker( + mask1 = DISABLE_CLOCK, + mask2 = DISABLE2_GLOBAL_ACTIONS + ) { callbackCalled = true } + underTest.startTracking(commandQueue, DISPLAY_ID) + + // First, update isDisabled to true + underTest.disable(DISPLAY_ID, state1 = DISABLE_CLOCK, state2 = 0, animate = false) + assertThat(underTest.isDisabled).isTrue() + + // WHEN isDisabled updates back to false + callbackCalled = false + underTest.disable(DISPLAY_ID, state1 = 0, state2 = 0, animate = false) + + // THEN the callback is called again + assertThat(callbackCalled).isTrue() + } + + @Test + fun disable_manyUpdates_isDisabledUpdatesAppropriately() { + underTest = DisableStateTracker( + mask1 = DISABLE_CLOCK or DISABLE_EXPAND or DISABLE_NAVIGATION, + mask2 = DISABLE2_GLOBAL_ACTIONS or DISABLE2_SYSTEM_ICONS + ) { } + underTest.startTracking(commandQueue, DISPLAY_ID) + + // All flags from mask1 -> isDisabled = true + underTest.disable( + DISPLAY_ID, + state1 = DISABLE_CLOCK or DISABLE_EXPAND or DISABLE_NAVIGATION, + state2 = 0, + animate = false + ) + assertThat(underTest.isDisabled).isTrue() + + // Irrelevant flags from mask1 -> isDisabled = false + underTest.disable( + DISPLAY_ID, + state1 = DISABLE_NOTIFICATION_ICONS or DISABLE_NOTIFICATION_TICKER, + state2 = 0, + animate = false + ) + assertThat(underTest.isDisabled).isFalse() + + // All flags from mask1 & all flags from mask2 -> isDisabled = true + underTest.disable( + DISPLAY_ID, + state1 = DISABLE_CLOCK or DISABLE_EXPAND or DISABLE_NAVIGATION, + state2 = DISABLE2_GLOBAL_ACTIONS or DISABLE2_SYSTEM_ICONS, + animate = false + ) + assertThat(underTest.isDisabled).isTrue() + + // No flags -> isDisabled = false + underTest.disable(DISPLAY_ID, state1 = 0, state2 = 0, animate = false) + assertThat(underTest.isDisabled).isFalse() + + // 1 flag from mask1 & 1 flag from mask2 -> isDisabled = true + underTest.disable( + DISPLAY_ID, + state1 = DISABLE_NAVIGATION, + state2 = DISABLE2_SYSTEM_ICONS, + animate = false + ) + assertThat(underTest.isDisabled).isTrue() + } + + companion object { + private const val DISPLAY_ID = 3 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 6cf1a12d94f0..ba5f5038c1d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -17,6 +17,9 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; +import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; + import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -49,6 +52,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker; @@ -118,6 +122,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private StatusBarUserInfoTracker mStatusBarUserInfoTracker; @Mock private SecureSettings mSecureSettings; + @Mock private CommandQueue mCommandQueue; private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider; private KeyguardStatusBarView mKeyguardStatusBarView; @@ -137,6 +142,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardStatusBarView = spy((KeyguardStatusBarView) LayoutInflater.from(mContext) .inflate(R.layout.keyguard_status_bar, null)); + when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay()); }); mController = createController(); @@ -165,6 +171,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mStatusBarUserSwitcherController, mStatusBarUserInfoTracker, mSecureSettings, + mCommandQueue, mFakeExecutor ); } @@ -176,6 +183,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { verify(mConfigurationController).addCallback(any()); verify(mAnimationScheduler).addCallback(any()); verify(mUserInfoController).addCallback(any()); + verify(mCommandQueue).addCallback(any()); verify(mStatusBarIconController).addIconGroup(any()); verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); } @@ -214,6 +222,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { verify(mConfigurationController).removeCallback(any()); verify(mAnimationScheduler).removeCallback(any()); verify(mUserInfoController).removeCallback(any()); + verify(mCommandQueue).removeCallback(any()); verify(mStatusBarIconController).removeIconGroup(any()); } @@ -280,6 +289,17 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() { + mController.onViewAttached(); + setDisableSystemIcons(true); + + mController.updateViewState(1f, View.VISIBLE); + + // Since we're disabled, we stay invisible + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test public void updateViewState_notKeyguardState_nothingUpdated() { mController.onViewAttached(); updateStateToNotKeyguard(); @@ -359,6 +379,50 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void updateViewState_disableSystemInfoFalse_viewShown() { + mController.onViewAttached(); + updateStateToKeyguard(); + setDisableSystemInfo(false); + + mController.updateViewState(); + + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateViewState_disableSystemInfoTrue_viewHidden() { + mController.onViewAttached(); + updateStateToKeyguard(); + setDisableSystemInfo(true); + + mController.updateViewState(); + + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test + public void updateViewState_disableSystemIconsFalse_viewShown() { + mController.onViewAttached(); + updateStateToKeyguard(); + setDisableSystemIcons(false); + + mController.updateViewState(); + + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateViewState_disableSystemIconsTrue_viewHidden() { + mController.onViewAttached(); + updateStateToKeyguard(); + setDisableSystemIcons(true); + + mController.updateViewState(); + + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test public void setAlpha_explicitAlpha_setsExplicitAlpha() { mController.onViewAttached(); updateStateToKeyguard(); @@ -485,6 +549,19 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { callback.onStateChanged(state); } + @Test + public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() { + mController.onViewAttached(); + updateStateToKeyguard(); + setDisableSystemInfo(true); + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); + + mController.animateKeyguardStatusBarIn(); + + // Since we're disabled, we don't actually animate in and stay invisible + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); + } + /** * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)} * to ensure values are updated properly. @@ -498,6 +575,25 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { callback.onFinishedGoingToSleep(0); } + private void setDisableSystemInfo(boolean disabled) { + CommandQueue.Callbacks callback = getCommandQueueCallback(); + int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0; + callback.disable(mContext.getDisplayId(), disabled1, 0, false); + } + + private void setDisableSystemIcons(boolean disabled) { + CommandQueue.Callbacks callback = getCommandQueueCallback(); + int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0; + callback.disable(mContext.getDisplayId(), 0, disabled2, false); + } + + private CommandQueue.Callbacks getCommandQueueCallback() { + ArgumentCaptor<CommandQueue.Callbacks> captor = + ArgumentCaptor.forClass(CommandQueue.Callbacks.class); + verify(mCommandQueue).addCallback(captor.capture()); + return captor.getValue(); + } + private static class TestNotificationPanelViewStateProvider implements NotificationPanelViewController.NotificationPanelViewStateProvider { |