diff options
4 files changed, 260 insertions, 59 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index fcae23b068bf..0d580792a9dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -14,9 +14,6 @@ package com.android.systemui.statusbar.phone.fragment; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; - import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Fragment; @@ -39,6 +36,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.animation.Animator; import com.android.app.animation.Interpolators; +import com.android.app.animation.InterpolatorsAndroidX; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -77,6 +75,8 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -99,12 +99,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private static final String EXTRA_PANEL_STATE = "panel_state"; public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager"; public static final int FADE_IN_DURATION = 320; + public static final int FADE_OUT_DURATION = 160; public static final int FADE_IN_DELAY = 50; + private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1; + private static final int SOURCE_OTHER = 2; private StatusBarFragmentComponent mStatusBarFragmentComponent; private PhoneStatusBarView mStatusBar; private final StatusBarStateController mStatusBarStateController; private final KeyguardStateController mKeyguardStateController; private final ShadeViewController mShadeViewController; + private MultiSourceMinAlphaController mEndSideAlphaController; private LinearLayout mEndSideContent; private View mClockView; private View mOngoingCallChip; @@ -149,7 +153,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } }; private OperatorNameViewController mOperatorNameViewController; - private StatusBarSystemEventAnimator mSystemEventAnimator; + private StatusBarSystemEventDefaultAnimator mSystemEventAnimator; private final CarrierConfigChangedListener mCarrierConfigCallback = new CarrierConfigChangedListener() { @@ -297,14 +301,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content); + mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent); mClockView = mStatusBar.findViewById(R.id.clock); mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); showEndSideContent(false); showClock(false); initOperatorName(); initNotificationIconArea(); - mSystemEventAnimator = - new StatusBarSystemEventAnimator(mEndSideContent, getResources()); + mSystemEventAnimator = getSystemEventAnimator(); mCarrierConfigTracker.addCallback(mCarrierConfigCallback); mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); @@ -593,18 +597,27 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { - animateHide(mEndSideContent, animate); + if (!animate) { + mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); + } else { + mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION, + InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0); + } } private void showEndSideContent(boolean animate) { - // Only show the system icon area if we are not currently animating - int state = mAnimationScheduler.getAnimationState(); - if (state == IDLE || state == SHOWING_PERSISTENT_DOT) { - animateShow(mEndSideContent, animate); + if (!animate) { + mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); + return; + } + if (mKeyguardStateController.isKeyguardFadingAway()) { + mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, + mKeyguardStateController.getKeyguardFadingAwayDuration(), + InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN, + mKeyguardStateController.getKeyguardFadingAwayDelay()); } else { - // We are in the middle of a system status event animation, which will animate the - // alpha (but not the visibility). Allow the view to become visible again - mEndSideContent.setVisibility(View.VISIBLE); + mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION, + InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY); } } @@ -671,7 +684,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue v.animate() .alpha(0f) - .setDuration(160) + .setDuration(FADE_OUT_DURATION) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) .withEndAction(() -> v.setVisibility(state)); @@ -754,6 +767,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot); } + private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() { + return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> { + mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR); + return Unit.INSTANCE; + }, (translationX) -> { + mEndSideContent.setTranslationX(translationX); + return Unit.INSTANCE; + }, /*isAnimationRunning*/ false); + } + private void updateStatusBarLocation(int left, int right) { int leftMargin = left - mStatusBar.getLeft(); int rightMargin = mStatusBar.getRight() - right; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt new file mode 100644 index 000000000000..c8836e4235dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 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.phone.fragment + +import android.view.View +import androidx.core.animation.Interpolator +import androidx.core.animation.ValueAnimator +import com.android.app.animation.InterpolatorsAndroidX + +/** + * A controller that keeps track of multiple sources applying alpha value changes to a view. It will + * always apply the minimum alpha value of all sources. + */ +internal class MultiSourceMinAlphaController +@JvmOverloads +constructor(private val view: View, private val initialAlpha: Float = 1f) { + + private val alphas = mutableMapOf<Int, Float>() + private val animators = mutableMapOf<Int, ValueAnimator>() + + /** + * Sets the alpha of the provided source and applies it to the view (if no other source has set + * a lower alpha currently). If an animator of the same source is still running (i.e. + * [animateToAlpha] was called before), that animator is cancelled. + */ + fun setAlpha(alpha: Float, sourceId: Int) { + animators[sourceId]?.cancel() + updateAlpha(alpha, sourceId) + } + + /** Animates to the alpha of the provided source. */ + fun animateToAlpha( + alpha: Float, + sourceId: Int, + duration: Long, + interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN, + startDelay: Long = 0 + ) { + animators[sourceId]?.cancel() + val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha) + animator.duration = duration + animator.startDelay = startDelay + animator.interpolator = interpolator + animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) } + animator.start() + animators[sourceId] = animator + } + + fun reset() { + alphas.clear() + animators.forEach { it.value.cancel() } + animators.clear() + applyAlphaToView() + } + + private fun updateAlpha(alpha: Float, sourceId: Int) { + alphas[sourceId] = alpha + applyAlphaToView() + } + + private fun applyAlphaToView() { + val minAlpha = getMinAlpha() + view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE + view.alpha = minAlpha + } + + private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 9b1d93b691c3..5dcb90144b70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -18,10 +18,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -49,7 +45,7 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; -import androidx.core.animation.Animator; +import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -85,6 +81,7 @@ import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -139,6 +136,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @ClassRule + public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>(); @@ -172,7 +171,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void testDisableSystemInfo_systemAnimationIdle_doesHide() { - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); @@ -192,24 +190,26 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + // WHEN the system event animation starts + fragment.onSystemEventAnimationBegin().start(); + + // THEN the view remains invisible during the animation + assertEquals(0f, getEndSideContentView().getAlpha(), 0.01); + mAnimatorTestRule.advanceTimeBy(500); + assertEquals(0f, getEndSideContentView().getAlpha(), 0.01); + // WHEN the disable flags are cleared during a system event animation - when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - // THEN the view is made visible again, but still low alpha - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the view remains invisible assertEquals(0, getEndSideContentView().getAlpha(), 0.01); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - Animator anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); // THEN the system info is full alpha assertEquals(1, getEndSideContentView().getAlpha(), 0.01); @@ -219,20 +219,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - Animator anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is - // still set) - assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // THEN the system info remains invisible (since the disable flag is still set) + assertEquals(0, getEndSideContentView().getAlpha(), 0.01); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -241,15 +236,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN); + assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // WHEN the system event animation begins - Animator anim = fragment.onSystemEventAnimationBegin(); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationBegin().start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is visible but alpha 0 - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the system info is invisible + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(0, getEndSideContentView().getAlpha(), 0.01); } @@ -257,25 +251,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN); + assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // WHEN the system event animation begins - Animator anim = fragment.onSystemEventAnimationBegin(); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationBegin().start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is visible but alpha 0 - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the system info is invisible + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(0, getEndSideContentView().getAlpha(), 0.01); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the syste info is full alpha and VISIBLE + // THEN the system info is full alpha and VISIBLE assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(1, getEndSideContentView().getAlpha(), 0.01); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt new file mode 100644 index 000000000000..2617613d1fc5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 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.phone.fragment + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import androidx.core.animation.AnimatorTestRule +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_SOURCE_1 = 1 +private const val TEST_SOURCE_2 = 2 +private const val TEST_ANIMATION_DURATION = 100L +private const val INITIAL_ALPHA = 1f + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class MultiSourceMinAlphaControllerTest : SysuiTestCase() { + + private val view = View(context) + private val multiSourceMinAlphaController = + MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA) + + @get:Rule val animatorTestRule = AnimatorTestRule() + + @Before + fun setup() { + multiSourceMinAlphaController.reset() + } + + @Test + fun testSetAlpha() { + multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testAnimateToAlpha() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0.5f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testReset() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0.5f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2) + multiSourceMinAlphaController.reset() + // advance time to ensure that animators are cancelled when the controller is reset + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) + assertEquals(INITIAL_ALPHA, view.alpha) + } + + @Test + fun testMinOfTwoSourcesIsApplied() { + multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1) + multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2) + assertEquals(0f, view.alpha) + multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testSetAlphaForSameSourceCancelsAnimator() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) + multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) + // verify that animation was cancelled and the setAlpha call overrides the alpha value of + // the animation + assertEquals(1f, view.alpha) + } +} |