Merge "Fix privacy chip overlapping with statusbar icons" into udc-qpr-dev

This commit is contained in:
Johannes Gallmann
2023-06-27 06:38:32 +00:00
committed by Android (Google) Code Review
4 changed files with 262 additions and 61 deletions

View File

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

View File

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

View File

@@ -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);
// WHEN the system event animation begins
Animator anim = fragment.onSystemEventAnimationBegin();
anim.start();
processAllMessages();
anim.end();
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
// THEN the system info is visible but alpha 0
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
// WHEN the system event animation begins
fragment.onSystemEventAnimationBegin().start();
mAnimatorTestRule.advanceTimeBy(500);
// 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);
// WHEN the system event animation begins
Animator anim = fragment.onSystemEventAnimationBegin();
anim.start();
processAllMessages();
anim.end();
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
// THEN the system info is visible but alpha 0
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
// WHEN the system event animation begins
fragment.onSystemEventAnimationBegin().start();
mAnimatorTestRule.advanceTimeBy(500);
// 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);
}

View File

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