Fold to AOD animation: part 2, add fold to aod controller
Adds animation that animates AOD content from left to right when folding a foldable device. There are still a couple of issue that should be addressed in the follow up CLs: * Navigation bar handle sometimes displayed on AOD * Showing keyguard when folding from unlocked state sometimes takes too much time so the screen blocker times out which leads to flickering Bug: 202844967 Test: fold from lockscreen, fold from unlocked device, aod enabled/disabled, gesture nav enabled/disabled Test: screen off animation when unlocked Test: screen off animation when locked Test: screen off animation when timed out Test: fingerprint unlock from aod Change-Id: Ie44f82119ac770ce34faecc60402044d4109dddf
This commit is contained in:
@@ -168,6 +168,12 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
|
||||
if (!mIsDozing) mView.animateAppearOnLockscreen();
|
||||
}
|
||||
|
||||
/** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
|
||||
* fully folded state and it goes to sleep (always on display screen) */
|
||||
public void animateFoldAppear() {
|
||||
mView.animateFoldAppear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the time for the view.
|
||||
*/
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import android.annotation.FloatRange;
|
||||
import android.annotation.IntRange;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
||||
* The time's text color is a gradient that changes its colors based on its controller.
|
||||
*/
|
||||
public class AnimatableClockView extends TextView {
|
||||
private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
|
||||
private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
|
||||
private static final long DOZE_ANIM_DURATION = 300;
|
||||
private static final long APPEAR_ANIM_DURATION = 350;
|
||||
private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
|
||||
private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
|
||||
|
||||
private final Calendar mTime = Calendar.getInstance();
|
||||
|
||||
private final int mDozingWeight;
|
||||
private final int mLockScreenWeight;
|
||||
private CharSequence mFormat;
|
||||
private CharSequence mDescFormat;
|
||||
private int mDozingColor;
|
||||
private int mLockScreenColor;
|
||||
private float mLineSpacingScale = 1f;
|
||||
private int mChargeAnimationDelay = 0;
|
||||
|
||||
private TextAnimator mTextAnimator = null;
|
||||
private Runnable mOnTextAnimatorInitialized;
|
||||
|
||||
private boolean mIsSingleLine;
|
||||
|
||||
public AnimatableClockView(Context context) {
|
||||
this(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
TypedArray ta = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes);
|
||||
try {
|
||||
mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
|
||||
mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
|
||||
mChargeAnimationDelay = ta.getInt(
|
||||
R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
|
||||
} finally {
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
ta = context.obtainStyledAttributes(
|
||||
attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
|
||||
try {
|
||||
mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false);
|
||||
} finally {
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
refreshFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
refreshFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
int getDozingWeight() {
|
||||
if (useBoldedVersion()) {
|
||||
return mDozingWeight + 100;
|
||||
}
|
||||
return mDozingWeight;
|
||||
}
|
||||
|
||||
int getLockScreenWeight() {
|
||||
if (useBoldedVersion()) {
|
||||
return mLockScreenWeight + 100;
|
||||
}
|
||||
return mLockScreenWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use a bolded version based on the user specified fontWeightAdjustment.
|
||||
*/
|
||||
boolean useBoldedVersion() {
|
||||
// "Bold text" fontWeightAdjustment is 300.
|
||||
return getResources().getConfiguration().fontWeightAdjustment > 100;
|
||||
}
|
||||
|
||||
void refreshTime() {
|
||||
mTime.setTimeInMillis(System.currentTimeMillis());
|
||||
setText(DateFormat.format(mFormat, mTime));
|
||||
setContentDescription(DateFormat.format(mDescFormat, mTime));
|
||||
}
|
||||
|
||||
void onTimeZoneChanged(TimeZone timeZone) {
|
||||
mTime.setTimeZone(timeZone);
|
||||
refreshFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mTextAnimator == null) {
|
||||
mTextAnimator = new TextAnimator(
|
||||
getLayout(),
|
||||
() -> {
|
||||
invalidate();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
if (mOnTextAnimatorInitialized != null) {
|
||||
mOnTextAnimatorInitialized.run();
|
||||
mOnTextAnimatorInitialized = null;
|
||||
}
|
||||
} else {
|
||||
mTextAnimator.updateLayout(getLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
mTextAnimator.draw(canvas);
|
||||
}
|
||||
|
||||
void setLineSpacingScale(float scale) {
|
||||
mLineSpacingScale = scale;
|
||||
setLineSpacing(0, mLineSpacingScale);
|
||||
}
|
||||
|
||||
void setColors(int dozingColor, int lockScreenColor) {
|
||||
mDozingColor = dozingColor;
|
||||
mLockScreenColor = lockScreenColor;
|
||||
}
|
||||
|
||||
void animateAppearOnLockscreen() {
|
||||
if (mTextAnimator == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTextStyle(
|
||||
getDozingWeight(),
|
||||
-1 /* text size, no update */,
|
||||
mLockScreenColor,
|
||||
false /* animate */,
|
||||
0 /* duration */,
|
||||
0 /* delay */,
|
||||
null /* onAnimationEnd */);
|
||||
|
||||
setTextStyle(
|
||||
getLockScreenWeight(),
|
||||
-1 /* text size, no update */,
|
||||
mLockScreenColor,
|
||||
true, /* animate */
|
||||
APPEAR_ANIM_DURATION,
|
||||
0 /* delay */,
|
||||
null /* onAnimationEnd */);
|
||||
}
|
||||
|
||||
void animateCharge(DozeStateGetter dozeStateGetter) {
|
||||
if (mTextAnimator == null || mTextAnimator.isRunning()) {
|
||||
// Skip charge animation if dozing animation is already playing.
|
||||
return;
|
||||
}
|
||||
Runnable startAnimPhase2 = () -> setTextStyle(
|
||||
dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */,
|
||||
-1,
|
||||
null,
|
||||
true /* animate */,
|
||||
CHARGE_ANIM_DURATION_PHASE_1,
|
||||
0 /* delay */,
|
||||
null /* onAnimationEnd */);
|
||||
setTextStyle(dozeStateGetter.isDozing()
|
||||
? getLockScreenWeight()
|
||||
: getDozingWeight()/* weight */,
|
||||
-1,
|
||||
null,
|
||||
true /* animate */,
|
||||
CHARGE_ANIM_DURATION_PHASE_0,
|
||||
mChargeAnimationDelay,
|
||||
startAnimPhase2);
|
||||
}
|
||||
|
||||
void animateDoze(boolean isDozing, boolean animate) {
|
||||
setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */,
|
||||
-1,
|
||||
isDozing ? mDozingColor : mLockScreenColor,
|
||||
animate,
|
||||
DOZE_ANIM_DURATION,
|
||||
0 /* delay */,
|
||||
null /* onAnimationEnd */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text style with an optional animation.
|
||||
*
|
||||
* By passing -1 to weight, the view preserves its current weight.
|
||||
* By passing -1 to textSize, the view preserves its current text size.
|
||||
*
|
||||
* @param weight text weight.
|
||||
* @param textSize font size.
|
||||
* @param animate true to animate the text style change, otherwise false.
|
||||
*/
|
||||
private void setTextStyle(
|
||||
@IntRange(from = 0, to = 1000) int weight,
|
||||
@FloatRange(from = 0) float textSize,
|
||||
Integer color,
|
||||
boolean animate,
|
||||
long duration,
|
||||
long delay,
|
||||
Runnable onAnimationEnd) {
|
||||
if (mTextAnimator != null) {
|
||||
mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
|
||||
delay, onAnimationEnd);
|
||||
} else {
|
||||
// when the text animator is set, update its start values
|
||||
mOnTextAnimatorInitialized =
|
||||
() -> mTextAnimator.setTextStyle(
|
||||
weight, textSize, color, false, duration, null,
|
||||
delay, onAnimationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void refreshFormat() {
|
||||
Patterns.update(mContext);
|
||||
|
||||
final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
|
||||
if (mIsSingleLine && use24HourFormat) {
|
||||
mFormat = Patterns.sClockView24;
|
||||
} else if (!mIsSingleLine && use24HourFormat) {
|
||||
mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
|
||||
} else if (mIsSingleLine && !use24HourFormat) {
|
||||
mFormat = Patterns.sClockView12;
|
||||
} else {
|
||||
mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
|
||||
}
|
||||
|
||||
mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12;
|
||||
refreshTime();
|
||||
}
|
||||
|
||||
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
|
||||
// This is an optimization to ensure we only recompute the patterns when the inputs change.
|
||||
private static final class Patterns {
|
||||
static String sClockView12;
|
||||
static String sClockView24;
|
||||
static String sCacheKey;
|
||||
|
||||
static void update(Context context) {
|
||||
final Locale locale = Locale.getDefault();
|
||||
final Resources res = context.getResources();
|
||||
final String clockView12Skel = res.getString(R.string.clock_12hr_format);
|
||||
final String clockView24Skel = res.getString(R.string.clock_24hr_format);
|
||||
final String key = locale.toString() + clockView12Skel + clockView24Skel;
|
||||
if (key.equals(sCacheKey)) return;
|
||||
sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
|
||||
|
||||
// CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
|
||||
// format. The following code removes the AM/PM indicator if we didn't want it.
|
||||
if (!clockView12Skel.contains("a")) {
|
||||
sClockView12 = sClockView12.replaceAll("a", "").trim();
|
||||
}
|
||||
sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
|
||||
sCacheKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
interface DozeStateGetter {
|
||||
boolean isDozing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.keyguard
|
||||
|
||||
import android.animation.TimeInterpolator
|
||||
import android.annotation.ColorInt
|
||||
import android.annotation.FloatRange
|
||||
import android.annotation.IntRange
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.text.format.DateFormat
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
/**
|
||||
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
||||
* The time's text color is a gradient that changes its colors based on its controller.
|
||||
*/
|
||||
class AnimatableClockView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private val time = Calendar.getInstance()
|
||||
|
||||
private val dozingWeightInternal: Int
|
||||
private val lockScreenWeightInternal: Int
|
||||
private val isSingleLineInternal: Boolean
|
||||
|
||||
private var format: CharSequence? = null
|
||||
private var descFormat: CharSequence? = null
|
||||
|
||||
@ColorInt
|
||||
private var dozingColor = 0
|
||||
|
||||
@ColorInt
|
||||
private var lockScreenColor = 0
|
||||
|
||||
private var lineSpacingScale = 1f
|
||||
private val chargeAnimationDelay: Int
|
||||
private var textAnimator: TextAnimator? = null
|
||||
private var onTextAnimatorInitialized: Runnable? = null
|
||||
|
||||
val dozingWeight: Int
|
||||
get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
|
||||
|
||||
val lockScreenWeight: Int
|
||||
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
|
||||
|
||||
init {
|
||||
val animatableClockViewAttributes = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
|
||||
)
|
||||
|
||||
try {
|
||||
dozingWeightInternal = animatableClockViewAttributes.getInt(
|
||||
R.styleable.AnimatableClockView_dozeWeight,
|
||||
100
|
||||
)
|
||||
lockScreenWeightInternal = animatableClockViewAttributes.getInt(
|
||||
R.styleable.AnimatableClockView_lockScreenWeight,
|
||||
300
|
||||
)
|
||||
chargeAnimationDelay = animatableClockViewAttributes.getInt(
|
||||
R.styleable.AnimatableClockView_chargeAnimationDelay, 200
|
||||
)
|
||||
} finally {
|
||||
animatableClockViewAttributes.recycle()
|
||||
}
|
||||
|
||||
val textViewAttributes = context.obtainStyledAttributes(
|
||||
attrs, android.R.styleable.TextView,
|
||||
defStyleAttr, defStyleRes
|
||||
)
|
||||
|
||||
isSingleLineInternal =
|
||||
try {
|
||||
textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
|
||||
} finally {
|
||||
textViewAttributes.recycle()
|
||||
}
|
||||
|
||||
refreshFormat()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
refreshFormat()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use a bolded version based on the user specified fontWeightAdjustment.
|
||||
*/
|
||||
fun useBoldedVersion(): Boolean {
|
||||
// "Bold text" fontWeightAdjustment is 300.
|
||||
return resources.configuration.fontWeightAdjustment > 100
|
||||
}
|
||||
|
||||
fun refreshTime() {
|
||||
time.timeInMillis = System.currentTimeMillis()
|
||||
text = DateFormat.format(format, time)
|
||||
contentDescription = DateFormat.format(descFormat, time)
|
||||
}
|
||||
|
||||
fun onTimeZoneChanged(timeZone: TimeZone?) {
|
||||
time.timeZone = timeZone
|
||||
refreshFormat()
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation")
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
val animator = textAnimator
|
||||
if (animator == null) {
|
||||
textAnimator = TextAnimator(layout) { invalidate() }
|
||||
onTextAnimatorInitialized?.run()
|
||||
onTextAnimatorInitialized = null
|
||||
} else {
|
||||
animator.updateLayout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
textAnimator?.draw(canvas)
|
||||
}
|
||||
|
||||
fun setLineSpacingScale(scale: Float) {
|
||||
lineSpacingScale = scale
|
||||
setLineSpacing(0f, lineSpacingScale)
|
||||
}
|
||||
|
||||
fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
|
||||
this.dozingColor = dozingColor
|
||||
this.lockScreenColor = lockScreenColor
|
||||
}
|
||||
|
||||
fun animateAppearOnLockscreen() {
|
||||
if (textAnimator == null) {
|
||||
return
|
||||
}
|
||||
setTextStyle(
|
||||
weight = dozingWeight,
|
||||
textSize = -1f,
|
||||
color = lockScreenColor,
|
||||
animate = false,
|
||||
duration = 0,
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
setTextStyle(
|
||||
weight = lockScreenWeight,
|
||||
textSize = -1f,
|
||||
color = lockScreenColor,
|
||||
animate = true,
|
||||
duration = APPEAR_ANIM_DURATION,
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
}
|
||||
|
||||
fun animateFoldAppear() {
|
||||
if (textAnimator == null) {
|
||||
return
|
||||
}
|
||||
setTextStyle(
|
||||
weight = lockScreenWeightInternal,
|
||||
textSize = -1f,
|
||||
color = lockScreenColor,
|
||||
animate = false,
|
||||
duration = 0,
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
setTextStyle(
|
||||
weight = dozingWeightInternal,
|
||||
textSize = -1f,
|
||||
color = dozingColor,
|
||||
animate = true,
|
||||
interpolator = Interpolators.EMPHASIZED_DECELERATE,
|
||||
duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
}
|
||||
|
||||
fun animateCharge(dozeStateGetter: DozeStateGetter) {
|
||||
if (textAnimator == null || textAnimator!!.isRunning()) {
|
||||
// Skip charge animation if dozing animation is already playing.
|
||||
return
|
||||
}
|
||||
val startAnimPhase2 = Runnable {
|
||||
setTextStyle(
|
||||
weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
|
||||
textSize = -1f,
|
||||
color = null,
|
||||
animate = true,
|
||||
duration = CHARGE_ANIM_DURATION_PHASE_1,
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
}
|
||||
setTextStyle(
|
||||
weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
|
||||
textSize = -1f,
|
||||
color = null,
|
||||
animate = true,
|
||||
duration = CHARGE_ANIM_DURATION_PHASE_0,
|
||||
delay = chargeAnimationDelay.toLong(),
|
||||
onAnimationEnd = startAnimPhase2
|
||||
)
|
||||
}
|
||||
|
||||
fun animateDoze(isDozing: Boolean, animate: Boolean) {
|
||||
setTextStyle(
|
||||
weight = if (isDozing) dozingWeight else lockScreenWeight,
|
||||
textSize = -1f,
|
||||
color = if (isDozing) dozingColor else lockScreenColor,
|
||||
animate = animate,
|
||||
duration = DOZE_ANIM_DURATION,
|
||||
delay = 0,
|
||||
onAnimationEnd = null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text style with an optional animation.
|
||||
*
|
||||
* By passing -1 to weight, the view preserves its current weight.
|
||||
* By passing -1 to textSize, the view preserves its current text size.
|
||||
*
|
||||
* @param weight text weight.
|
||||
* @param textSize font size.
|
||||
* @param animate true to animate the text style change, otherwise false.
|
||||
*/
|
||||
private fun setTextStyle(
|
||||
@IntRange(from = 0, to = 1000) weight: Int,
|
||||
@FloatRange(from = 0.0) textSize: Float,
|
||||
color: Int?,
|
||||
animate: Boolean,
|
||||
interpolator: TimeInterpolator?,
|
||||
duration: Long,
|
||||
delay: Long,
|
||||
onAnimationEnd: Runnable?
|
||||
) {
|
||||
if (textAnimator != null) {
|
||||
textAnimator?.setTextStyle(
|
||||
weight = weight,
|
||||
textSize = textSize,
|
||||
color = color,
|
||||
animate = animate,
|
||||
duration = duration,
|
||||
interpolator = interpolator,
|
||||
delay = delay,
|
||||
onAnimationEnd = onAnimationEnd
|
||||
)
|
||||
} else {
|
||||
// when the text animator is set, update its start values
|
||||
onTextAnimatorInitialized = Runnable {
|
||||
textAnimator?.setTextStyle(
|
||||
weight = weight,
|
||||
textSize = textSize,
|
||||
color = color,
|
||||
animate = false,
|
||||
duration = duration,
|
||||
interpolator = interpolator,
|
||||
delay = delay,
|
||||
onAnimationEnd = onAnimationEnd
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTextStyle(
|
||||
@IntRange(from = 0, to = 1000) weight: Int,
|
||||
@FloatRange(from = 0.0) textSize: Float,
|
||||
color: Int?,
|
||||
animate: Boolean,
|
||||
duration: Long,
|
||||
delay: Long,
|
||||
onAnimationEnd: Runnable?
|
||||
) {
|
||||
setTextStyle(
|
||||
weight = weight,
|
||||
textSize = textSize,
|
||||
color = color,
|
||||
animate = animate,
|
||||
interpolator = null,
|
||||
duration = duration,
|
||||
delay = delay,
|
||||
onAnimationEnd = onAnimationEnd
|
||||
)
|
||||
}
|
||||
|
||||
fun refreshFormat() {
|
||||
Patterns.update(context)
|
||||
val use24HourFormat = DateFormat.is24HourFormat(context)
|
||||
|
||||
format = when {
|
||||
isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
|
||||
!isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
|
||||
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
|
||||
else -> DOUBLE_LINE_FORMAT_12_HOUR
|
||||
}
|
||||
|
||||
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
|
||||
|
||||
refreshTime()
|
||||
}
|
||||
|
||||
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
|
||||
// This is an optimization to ensure we only recompute the patterns when the inputs change.
|
||||
private object Patterns {
|
||||
var sClockView12: String? = null
|
||||
var sClockView24: String? = null
|
||||
var sCacheKey: String? = null
|
||||
|
||||
fun update(context: Context) {
|
||||
val locale = Locale.getDefault()
|
||||
val res = context.resources
|
||||
val clockView12Skel = res.getString(R.string.clock_12hr_format)
|
||||
val clockView24Skel = res.getString(R.string.clock_24hr_format)
|
||||
val key = locale.toString() + clockView12Skel + clockView24Skel
|
||||
if (key == sCacheKey) return
|
||||
|
||||
val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
|
||||
sClockView12 = clockView12
|
||||
|
||||
// CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
|
||||
// format. The following code removes the AM/PM indicator if we didn't want it.
|
||||
if (!clockView12Skel.contains("a")) {
|
||||
sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
|
||||
}
|
||||
sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
|
||||
sCacheKey = key
|
||||
}
|
||||
}
|
||||
|
||||
interface DozeStateGetter {
|
||||
val isDozing: Boolean
|
||||
}
|
||||
}
|
||||
|
||||
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
|
||||
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
|
||||
private const val DOZE_ANIM_DURATION: Long = 300
|
||||
private const val APPEAR_ANIM_DURATION: Long = 350
|
||||
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
|
||||
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
|
||||
@@ -190,11 +190,15 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void animateClockChange(boolean useLargeClock) {
|
||||
private void updateClockViews(boolean useLargeClock, boolean animate) {
|
||||
if (mClockInAnim != null) mClockInAnim.cancel();
|
||||
if (mClockOutAnim != null) mClockOutAnim.cancel();
|
||||
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
|
||||
|
||||
mClockInAnim = null;
|
||||
mClockOutAnim = null;
|
||||
mStatusAreaAnim = null;
|
||||
|
||||
View in, out;
|
||||
int direction = 1;
|
||||
float statusAreaYTranslation;
|
||||
@@ -214,6 +218,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
removeView(out);
|
||||
}
|
||||
|
||||
if (!animate) {
|
||||
out.setAlpha(0f);
|
||||
in.setAlpha(1f);
|
||||
in.setVisibility(VISIBLE);
|
||||
mStatusArea.setTranslationY(statusAreaYTranslation);
|
||||
return;
|
||||
}
|
||||
|
||||
mClockOutAnim = new AnimatorSet();
|
||||
mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
|
||||
mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
|
||||
@@ -273,7 +285,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
*
|
||||
* @return true if desired clock appeared and false if it was already visible
|
||||
*/
|
||||
boolean switchToClock(@ClockSize int clockSize) {
|
||||
boolean switchToClock(@ClockSize int clockSize, boolean animate) {
|
||||
if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
|
||||
return false;
|
||||
}
|
||||
@@ -281,7 +293,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
// let's make sure clock is changed only after all views were laid out so we can
|
||||
// translate them properly
|
||||
if (mChildrenAreLaidOut) {
|
||||
animateClockChange(clockSize == LARGE);
|
||||
updateClockViews(clockSize == LARGE, animate);
|
||||
}
|
||||
|
||||
mDisplayedClockSize = clockSize;
|
||||
@@ -293,7 +305,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
|
||||
if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
|
||||
animateClockChange(mDisplayedClockSize == LARGE);
|
||||
updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true);
|
||||
}
|
||||
|
||||
mChildrenAreLaidOut = true;
|
||||
|
||||
@@ -292,17 +292,24 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
* Set which clock should be displayed on the keyguard. The other one will be automatically
|
||||
* hidden.
|
||||
*/
|
||||
public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
|
||||
public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
|
||||
if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean appeared = mView.switchToClock(clockSize);
|
||||
if (appeared && clockSize == LARGE) {
|
||||
boolean appeared = mView.switchToClock(clockSize, animate);
|
||||
if (animate && appeared && clockSize == LARGE) {
|
||||
mLargeClockViewController.animateAppear();
|
||||
}
|
||||
}
|
||||
|
||||
public void animateFoldToAod() {
|
||||
if (mClockViewController != null) {
|
||||
mClockViewController.animateFoldAppear();
|
||||
mLargeClockViewController.animateFoldAppear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're presenting a custom clock of just the default one.
|
||||
*/
|
||||
@@ -445,7 +452,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
|
||||
|
||||
if (!mCanShowDoubleLineClock) {
|
||||
mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
|
||||
mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +123,17 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
|
||||
* Set which clock should be displayed on the keyguard. The other one will be automatically
|
||||
* hidden.
|
||||
*/
|
||||
public void displayClock(@ClockSize int clockSize) {
|
||||
mKeyguardClockSwitchController.displayClock(clockSize);
|
||||
public void displayClock(@ClockSize int clockSize, boolean animate) {
|
||||
mKeyguardClockSwitchController.displayClock(clockSize, animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs fold to aod animation of the clocks (changes font weight from bold to thin).
|
||||
* This animation is played when AOD is enabled and foldable device is fully folded, it is
|
||||
* displayed on the outer screen
|
||||
*/
|
||||
public void animateFoldToAod() {
|
||||
mKeyguardClockSwitchController.animateFoldToAod();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,10 +37,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.unfold.FoldAodAnimationController;
|
||||
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus;
|
||||
import com.android.systemui.unfold.SysUIUnfoldComponent;
|
||||
import com.android.systemui.util.AlarmTimeout;
|
||||
import com.android.systemui.util.wakelock.WakeLock;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -49,7 +53,8 @@ import javax.inject.Inject;
|
||||
*/
|
||||
@DozeScope
|
||||
public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
ConfigurationController.ConfigurationListener, StatusBarStateController.StateListener {
|
||||
ConfigurationController.ConfigurationListener, FoldAodAnimationStatus,
|
||||
StatusBarStateController.StateListener {
|
||||
// if enabled, calls dozeTimeTick() whenever the time changes:
|
||||
private static final boolean BURN_IN_TESTING_ENABLED = false;
|
||||
private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
|
||||
@@ -57,6 +62,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
private final DozeHost mHost;
|
||||
private final Handler mHandler;
|
||||
private final WakeLock mWakeLock;
|
||||
private final FoldAodAnimationController mFoldAodAnimationController;
|
||||
private DozeMachine mMachine;
|
||||
private final AlarmTimeout mTimeTicker;
|
||||
private final boolean mCanAnimateTransition;
|
||||
@@ -100,6 +106,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
|
||||
DozeLog dozeLog, TunerService tunerService,
|
||||
StatusBarStateController statusBarStateController,
|
||||
Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
|
||||
ConfigurationController configurationController) {
|
||||
mContext = context;
|
||||
mWakeLock = wakeLock;
|
||||
@@ -118,12 +125,23 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
|
||||
mConfigurationController = configurationController;
|
||||
mConfigurationController.addCallback(this);
|
||||
|
||||
mFoldAodAnimationController = sysUiUnfoldComponent
|
||||
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
|
||||
|
||||
if (mFoldAodAnimationController != null) {
|
||||
mFoldAodAnimationController.addCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
mTunerService.removeTunable(this);
|
||||
mConfigurationController.removeCallback(this);
|
||||
|
||||
if (mFoldAodAnimationController != null) {
|
||||
mFoldAodAnimationController.removeCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,7 +160,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
&& (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff())
|
||||
&& !mHost.isPowerSaveActive();
|
||||
mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
|
||||
mHost.setAnimateScreenOff(controlScreenOff);
|
||||
mHost.setAnimateScreenOff(controlScreenOff
|
||||
&& mDozeParameters.shouldAnimateDozingChange());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,4 +318,9 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
|
||||
public void onStatePostChange() {
|
||||
updateAnimateScreenOff();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFoldToAodAnimationChanged() {
|
||||
updateAnimateScreenOff();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.unfold.FoldAodAnimationController;
|
||||
import com.android.systemui.unfold.SysUIUnfoldComponent;
|
||||
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
@@ -131,7 +132,6 @@ import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
@@ -439,7 +439,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
private boolean mInGestureNavigationMode;
|
||||
|
||||
private boolean mWakeAndUnlocking;
|
||||
private IKeyguardDrawnCallback mDrawnCallback;
|
||||
private Runnable mWakeAndUnlockingDrawnCallback;
|
||||
private CharSequence mCustomMessage;
|
||||
|
||||
/**
|
||||
@@ -817,7 +817,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
private DozeParameters mDozeParameters;
|
||||
|
||||
private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
|
||||
private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
|
||||
private final Optional<FoldAodAnimationController> mFoldAodAnimationController;
|
||||
private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer();
|
||||
|
||||
private final KeyguardStateController mKeyguardStateController;
|
||||
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
|
||||
@@ -877,8 +878,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
|
||||
}));
|
||||
mDozeParameters = dozeParameters;
|
||||
mUnfoldLightRevealAnimation = unfoldComponent.map(
|
||||
c -> c.getUnfoldLightRevealOverlayAnimation());
|
||||
|
||||
mUnfoldLightRevealAnimation = unfoldComponent
|
||||
.map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation);
|
||||
mFoldAodAnimationController = unfoldComponent
|
||||
.map(SysUIUnfoldComponent::getFoldAodAnimationController);
|
||||
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
statusBarStateController.addCallback(this);
|
||||
|
||||
@@ -1069,7 +1074,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
mDeviceInteractive = false;
|
||||
mGoingToSleep = false;
|
||||
mWakeAndUnlocking = false;
|
||||
mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff();
|
||||
mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange();
|
||||
|
||||
resetKeyguardDonePendingLocked();
|
||||
mHideAnimationRun = false;
|
||||
@@ -2221,14 +2226,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
|
||||
mKeyguardExitAnimationRunner = null;
|
||||
|
||||
if (mWakeAndUnlocking && mDrawnCallback != null) {
|
||||
if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) {
|
||||
|
||||
// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
|
||||
// the next draw from here so we don't have to wait for window manager to signal
|
||||
// this to our ViewRootImpl.
|
||||
mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
|
||||
notifyDrawn(mDrawnCallback);
|
||||
mDrawnCallback = null;
|
||||
mWakeAndUnlockingDrawnCallback.run();
|
||||
mWakeAndUnlockingDrawnCallback = null;
|
||||
}
|
||||
|
||||
LatencyTracker.getInstance(mContext)
|
||||
@@ -2566,31 +2571,27 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
synchronized (KeyguardViewMediator.this) {
|
||||
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
|
||||
|
||||
if (mUnfoldLightRevealAnimation.isPresent()) {
|
||||
mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
|
||||
mPendingDrawnTasks.reset();
|
||||
|
||||
if (mUnfoldLightRevealAnimation.isPresent()) {
|
||||
mUnfoldLightRevealAnimation.get()
|
||||
.onScreenTurningOn(() -> {
|
||||
if (mPendingDrawnTasks.decrementAndGet() == 0) {
|
||||
try {
|
||||
callback.onDrawn();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Exception calling onDrawn():", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mPendingDrawnTasks.set(1); // only keyguard drawn
|
||||
.onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal"));
|
||||
}
|
||||
|
||||
if (mFoldAodAnimationController.isPresent()) {
|
||||
mFoldAodAnimationController.get()
|
||||
.onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod"));
|
||||
}
|
||||
|
||||
mKeyguardViewControllerLazy.get().onScreenTurningOn();
|
||||
if (callback != null) {
|
||||
if (mWakeAndUnlocking) {
|
||||
mDrawnCallback = callback;
|
||||
} else {
|
||||
notifyDrawn(callback);
|
||||
mWakeAndUnlockingDrawnCallback =
|
||||
mPendingDrawnTasks.registerTask("wake-and-unlocking");
|
||||
}
|
||||
}
|
||||
|
||||
mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback));
|
||||
}
|
||||
Trace.endSection();
|
||||
}
|
||||
@@ -2599,6 +2600,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
|
||||
synchronized (this) {
|
||||
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
|
||||
|
||||
mPendingDrawnTasks.reset();
|
||||
mKeyguardViewControllerLazy.get().onScreenTurnedOn();
|
||||
}
|
||||
Trace.endSection();
|
||||
@@ -2607,18 +2610,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
private void handleNotifyScreenTurnedOff() {
|
||||
synchronized (this) {
|
||||
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff");
|
||||
mDrawnCallback = null;
|
||||
mWakeAndUnlockingDrawnCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
|
||||
Trace.beginSection("KeyguardViewMediator#notifyDrawn");
|
||||
if (mPendingDrawnTasks.decrementAndGet() == 0) {
|
||||
try {
|
||||
try {
|
||||
if (callback != null) {
|
||||
callback.onDrawn();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Exception calling onDrawn():", e);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Exception calling onDrawn():", e);
|
||||
}
|
||||
Trace.endSection();
|
||||
}
|
||||
@@ -2777,9 +2780,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
|
||||
pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun);
|
||||
pw.print(" mPendingReset: "); pw.println(mPendingReset);
|
||||
pw.print(" mPendingLock: "); pw.println(mPendingLock);
|
||||
pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
|
||||
pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount());
|
||||
pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
|
||||
pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback);
|
||||
pw.print(" mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.keyguard
|
||||
|
||||
import android.os.Trace
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
* Allows to wait for multiple callbacks and notify when the last one is executed
|
||||
*/
|
||||
class PendingDrawnTasksContainer {
|
||||
|
||||
private lateinit var pendingDrawnTasksCount: AtomicInteger
|
||||
private var completionCallback: AtomicReference<Runnable> = AtomicReference()
|
||||
|
||||
/**
|
||||
* Registers a task that we should wait for
|
||||
* @return a runnable that should be invoked when the task is finished
|
||||
*/
|
||||
fun registerTask(name: String): Runnable {
|
||||
pendingDrawnTasksCount.incrementAndGet()
|
||||
|
||||
if (ENABLE_TRACE) {
|
||||
Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0)
|
||||
}
|
||||
|
||||
return Runnable {
|
||||
if (pendingDrawnTasksCount.decrementAndGet() == 0) {
|
||||
val onComplete = completionCallback.getAndSet(null)
|
||||
onComplete?.run()
|
||||
|
||||
if (ENABLE_TRACE) {
|
||||
Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears state and initializes the container
|
||||
*/
|
||||
fun reset() {
|
||||
// Create new objects in case if there are pending callbacks from the previous invocations
|
||||
completionCallback = AtomicReference()
|
||||
pendingDrawnTasksCount = AtomicInteger(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts waiting for all tasks to be completed
|
||||
* When all registered tasks complete it will invoke the [onComplete] callback
|
||||
*/
|
||||
fun onTasksComplete(onComplete: Runnable) {
|
||||
completionCallback.set(onComplete)
|
||||
|
||||
if (pendingDrawnTasksCount.get() == 0) {
|
||||
val currentOnComplete = completionCallback.getAndSet(null)
|
||||
currentOnComplete?.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current pending tasks count
|
||||
*/
|
||||
fun getPendingCount(): Int = pendingDrawnTasksCount.get()
|
||||
}
|
||||
|
||||
private const val ENABLE_TRACE = false
|
||||
@@ -51,6 +51,7 @@ public class StackStateAnimator {
|
||||
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
|
||||
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
|
||||
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
|
||||
public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
|
||||
public static final int ANIMATION_DURATION_PULSE_APPEAR =
|
||||
KeyguardSliceView.DEFAULT_ANIM_DURATION;
|
||||
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
|
||||
|
||||
@@ -243,6 +243,10 @@ public class DozeParameters implements
|
||||
return mScreenOffAnimationController.shouldShowLightRevealScrim();
|
||||
}
|
||||
|
||||
public boolean shouldAnimateDozingChange() {
|
||||
return mScreenOffAnimationController.shouldAnimateDozingChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we're capable of controlling the screen off animation if we want to. This isn't
|
||||
* possible if AOD isn't even enabled or if the flag is disabled.
|
||||
@@ -324,6 +328,7 @@ public class DozeParameters implements
|
||||
for (Callback callback : mCallbacks) {
|
||||
callback.onAlwaysOnChange();
|
||||
}
|
||||
mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,6 +26,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.TOP;
|
||||
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
|
||||
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
|
||||
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
|
||||
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
|
||||
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
|
||||
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
@@ -34,6 +35,7 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
|
||||
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
|
||||
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
|
||||
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
|
||||
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
|
||||
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
|
||||
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
|
||||
@@ -1425,11 +1427,12 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
.getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
|
||||
boolean splitShadeWithActiveMedia =
|
||||
mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
|
||||
boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
|
||||
if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
|
||||
|| (splitShadeWithActiveMedia && !mDozing)) {
|
||||
mKeyguardStatusViewController.displayClock(SMALL);
|
||||
mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange);
|
||||
} else {
|
||||
mKeyguardStatusViewController.displayClock(LARGE);
|
||||
mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
|
||||
}
|
||||
updateKeyguardStatusViewAlignment(true /* animate */);
|
||||
int userIconHeight = mKeyguardQsUserSwitchController != null
|
||||
@@ -1465,7 +1468,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
mKeyguardStatusViewController.isClockTopAligned());
|
||||
mClockPositionAlgorithm.run(mClockPositionResult);
|
||||
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
|
||||
boolean animateClock = animate || mAnimateNextPositionUpdate;
|
||||
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
|
||||
mKeyguardStatusViewController.updatePosition(
|
||||
mClockPositionResult.clockX, mClockPositionResult.clockY,
|
||||
mClockPositionResult.clockScale, animateClock);
|
||||
@@ -3826,6 +3829,45 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the views to the initial state for the fold to AOD animation
|
||||
*/
|
||||
public void prepareFoldToAodAnimation() {
|
||||
// Force show AOD UI even if we are not locked
|
||||
showAodUi();
|
||||
|
||||
// Move the content of the AOD all the way to the left
|
||||
// so we can animate to the initial position
|
||||
final int translationAmount = mView.getResources().getDimensionPixelSize(
|
||||
R.dimen.below_clock_padding_start);
|
||||
mView.setTranslationX(-translationAmount);
|
||||
mView.setAlpha(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts fold to AOD animation
|
||||
*/
|
||||
public void startFoldToAodAnimation(Runnable endAction) {
|
||||
mView.animate()
|
||||
.translationX(0)
|
||||
.alpha(1f)
|
||||
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
|
||||
.setInterpolator(EMPHASIZED_DECELERATE)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
endAction.run();
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
endAction.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
|
||||
mKeyguardStatusViewController.animateFoldToAod();
|
||||
}
|
||||
|
||||
/** */
|
||||
public void setImportantForAccessibility(int mode) {
|
||||
mView.setImportantForAccessibility(mode);
|
||||
@@ -3940,6 +3982,10 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
mView.setAlpha(alpha);
|
||||
}
|
||||
|
||||
public void resetTranslation() {
|
||||
mView.setTranslationX(0f);
|
||||
}
|
||||
|
||||
public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
|
||||
return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
|
||||
durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
|
||||
|
||||
@@ -19,16 +19,23 @@ import android.view.View
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||
import com.android.systemui.statusbar.LightRevealScrim
|
||||
import com.android.systemui.unfold.FoldAodAnimationController
|
||||
import com.android.systemui.unfold.SysUIUnfoldComponent
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
|
||||
@SysUISingleton
|
||||
class ScreenOffAnimationController @Inject constructor(
|
||||
sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>,
|
||||
unlockedScreenOffAnimation: UnlockedScreenOffAnimationController,
|
||||
private val wakefulnessLifecycle: WakefulnessLifecycle,
|
||||
) : WakefulnessLifecycle.Observer {
|
||||
|
||||
// TODO(b/202844967) add fold to aod animation here
|
||||
private val animations: List<ScreenOffAnimation> = listOf(unlockedScreenOffAnimation)
|
||||
private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent
|
||||
.orElse(null)?.getFoldAodAnimationController()
|
||||
|
||||
private val animations: List<ScreenOffAnimation> =
|
||||
listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
|
||||
|
||||
fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
|
||||
animations.forEach { it.initialize(statusBar, lightRevealScrim) }
|
||||
@@ -42,6 +49,19 @@ class ScreenOffAnimationController @Inject constructor(
|
||||
animations.firstOrNull { it.startAnimation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when opaqueness of the light reveal scrim has change
|
||||
* When [isOpaque] is true then scrim is visible and covers the screen
|
||||
*/
|
||||
fun onScrimOpaqueChanged(isOpaque: Boolean) =
|
||||
animations.forEach { it.onScrimOpaqueChanged(isOpaque) }
|
||||
|
||||
/**
|
||||
* Called when always on display setting changed
|
||||
*/
|
||||
fun onAlwaysOnChanged(alwaysOn: Boolean) =
|
||||
animations.forEach { it.onAlwaysOnChanged(alwaysOn) }
|
||||
|
||||
/**
|
||||
* If returns true we are taking over the screen off animation from display manager to SysUI.
|
||||
* We can play our custom animation instead of default fade out animation.
|
||||
@@ -102,6 +122,12 @@ class ScreenOffAnimationController @Inject constructor(
|
||||
fun isKeyguardShowDelayed(): Boolean =
|
||||
animations.any { it.isAnimationPlaying() }
|
||||
|
||||
/**
|
||||
* Return true to ignore requests to hide keyguard
|
||||
*/
|
||||
fun isKeyguardHideDelayed(): Boolean =
|
||||
animations.any { it.isKeyguardHideDelayed() }
|
||||
|
||||
/**
|
||||
* Return true to make the StatusBar expanded so we can animate [LightRevealScrim]
|
||||
*/
|
||||
@@ -145,6 +171,19 @@ class ScreenOffAnimationController @Inject constructor(
|
||||
*/
|
||||
fun shouldAnimateAodIcons(): Boolean =
|
||||
animations.all { it.shouldAnimateAodIcons() }
|
||||
|
||||
/**
|
||||
* Return true to animate doze state change, if returns false dozing will be applied without
|
||||
* animation (sends only 0.0f or 1.0f dozing progress)
|
||||
*/
|
||||
fun shouldAnimateDozingChange(): Boolean =
|
||||
animations.all { it.shouldAnimateDozingChange() }
|
||||
|
||||
/**
|
||||
* Return true to animate large <-> small clock transition
|
||||
*/
|
||||
fun shouldAnimateClockChange(): Boolean =
|
||||
animations.all { it.shouldAnimateClockChange() }
|
||||
}
|
||||
|
||||
interface ScreenOffAnimation {
|
||||
@@ -158,11 +197,17 @@ interface ScreenOffAnimation {
|
||||
fun shouldPlayAnimation(): Boolean = false
|
||||
fun isAnimationPlaying(): Boolean = false
|
||||
|
||||
fun onScrimOpaqueChanged(isOpaque: Boolean) {}
|
||||
fun onAlwaysOnChanged(alwaysOn: Boolean) {}
|
||||
|
||||
fun shouldAnimateInKeyguard(): Boolean = false
|
||||
fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
|
||||
|
||||
fun isKeyguardHideDelayed(): Boolean = false
|
||||
fun shouldHideScrimOnWakeUp(): Boolean = false
|
||||
fun overrideNotificationsDozeAmount(): Boolean = false
|
||||
fun shouldShowAodIconsWhenShade(): Boolean = false
|
||||
fun shouldAnimateAodIcons(): Boolean = true
|
||||
fun shouldAnimateDozingChange(): Boolean = true
|
||||
fun shouldAnimateClockChange(): Boolean = true
|
||||
}
|
||||
|
||||
@@ -1234,6 +1234,8 @@ public class StatusBar extends CoreStartable implements
|
||||
Runnable updateOpaqueness = () -> {
|
||||
mNotificationShadeWindowController.setLightRevealScrimOpaque(
|
||||
mLightRevealScrim.isScrimOpaque());
|
||||
mScreenOffAnimationController
|
||||
.onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque());
|
||||
};
|
||||
if (opaque) {
|
||||
// Delay making the view opaque for a frame, because it needs some time to render
|
||||
@@ -2955,7 +2957,17 @@ public class StatusBar extends CoreStartable implements
|
||||
showKeyguardImpl();
|
||||
}
|
||||
} else {
|
||||
return hideKeyguardImpl(force);
|
||||
// During folding a foldable device this might be called as a result of
|
||||
// 'onScreenTurnedOff' call for the inner display.
|
||||
// In this case:
|
||||
// * When phone is locked on folding: it doesn't make sense to hide keyguard as it
|
||||
// will be immediately locked again
|
||||
// * When phone is unlocked: we still don't want to execute hiding of the keyguard
|
||||
// as the animation could prepare 'fake AOD' interface (without actually
|
||||
// transitioning to keyguard state) and this might reset the view states
|
||||
if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
|
||||
return hideKeyguardImpl(force);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3118,6 +3130,7 @@ public class StatusBar extends CoreStartable implements
|
||||
mNotificationPanelViewController.onAffordanceLaunchEnded();
|
||||
mNotificationPanelViewController.cancelAnimation();
|
||||
mNotificationPanelViewController.setAlpha(1f);
|
||||
mNotificationPanelViewController.resetTranslation();
|
||||
mNotificationPanelViewController.resetViewGroupFade();
|
||||
updateDozingState();
|
||||
updateScrimController();
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.unfold
|
||||
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator
|
||||
import com.android.systemui.keyguard.ScreenLifecycle
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||
import com.android.systemui.statusbar.LightRevealScrim
|
||||
import com.android.systemui.statusbar.phone.ScreenOffAnimation
|
||||
import com.android.systemui.statusbar.phone.StatusBar
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
|
||||
import com.android.systemui.util.settings.GlobalSettings
|
||||
import dagger.Lazy
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Controls folding to AOD animation: when AOD is enabled and foldable device is folded
|
||||
* we play a special AOD animation on the outer screen
|
||||
*/
|
||||
@SysUIUnfoldScope
|
||||
class FoldAodAnimationController @Inject constructor(
|
||||
private val screenLifecycle: ScreenLifecycle,
|
||||
private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
|
||||
private val wakefulnessLifecycle: WakefulnessLifecycle,
|
||||
private val globalSettings: GlobalSettings
|
||||
) : ScreenLifecycle.Observer,
|
||||
CallbackController<FoldAodAnimationStatus>,
|
||||
ScreenOffAnimation,
|
||||
WakefulnessLifecycle.Observer {
|
||||
|
||||
private var alwaysOnEnabled: Boolean = false
|
||||
private var isScrimOpaque: Boolean = false
|
||||
private lateinit var statusBar: StatusBar
|
||||
private var pendingScrimReadyCallback: Runnable? = null
|
||||
|
||||
private var shouldPlayAnimation = false
|
||||
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
|
||||
|
||||
private var isAnimationPlaying = false
|
||||
|
||||
override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
|
||||
this.statusBar = statusBar
|
||||
|
||||
screenLifecycle.addObserver(this)
|
||||
wakefulnessLifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should run fold to AOD animation
|
||||
*/
|
||||
override fun shouldPlayAnimation(): Boolean =
|
||||
shouldPlayAnimation
|
||||
|
||||
override fun startAnimation(): Boolean =
|
||||
if (alwaysOnEnabled &&
|
||||
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
|
||||
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
|
||||
) {
|
||||
shouldPlayAnimation = true
|
||||
|
||||
isAnimationPlaying = true
|
||||
statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
|
||||
|
||||
statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
|
||||
|
||||
true
|
||||
} else {
|
||||
shouldPlayAnimation = false
|
||||
false
|
||||
}
|
||||
|
||||
override fun onStartedWakingUp() {
|
||||
shouldPlayAnimation = false
|
||||
isAnimationPlaying = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when screen starts turning on, the contents of the screen might not be visible yet.
|
||||
* This method reports back that the animation is ready in [onReady] callback.
|
||||
*
|
||||
* @param onReady callback when the animation is ready
|
||||
* @see [com.android.systemui.keyguard.KeyguardViewMediator]
|
||||
*/
|
||||
fun onScreenTurningOn(onReady: Runnable) {
|
||||
if (shouldPlayAnimation) {
|
||||
if (isScrimOpaque) {
|
||||
onReady.run()
|
||||
} else {
|
||||
pendingScrimReadyCallback = onReady
|
||||
}
|
||||
} else {
|
||||
// No animation, call ready callback immediately
|
||||
onReady.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when keyguard scrim opaque changed
|
||||
*/
|
||||
override fun onScrimOpaqueChanged(isOpaque: Boolean) {
|
||||
isScrimOpaque = isOpaque
|
||||
|
||||
if (isOpaque) {
|
||||
pendingScrimReadyCallback?.run()
|
||||
pendingScrimReadyCallback = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScreenTurnedOn() {
|
||||
if (shouldPlayAnimation) {
|
||||
statusBar.notificationPanelViewController.startFoldToAodAnimation {
|
||||
// End action
|
||||
isAnimationPlaying = false
|
||||
keyguardViewMediatorLazy.get().maybeHandlePendingLock()
|
||||
}
|
||||
shouldPlayAnimation = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isAnimationPlaying(): Boolean =
|
||||
isAnimationPlaying
|
||||
|
||||
override fun isKeyguardHideDelayed(): Boolean =
|
||||
isAnimationPlaying()
|
||||
|
||||
override fun shouldShowAodIconsWhenShade(): Boolean =
|
||||
shouldPlayAnimation()
|
||||
|
||||
override fun shouldAnimateAodIcons(): Boolean =
|
||||
!shouldPlayAnimation()
|
||||
|
||||
override fun shouldAnimateDozingChange(): Boolean =
|
||||
!shouldPlayAnimation()
|
||||
|
||||
override fun shouldAnimateClockChange(): Boolean =
|
||||
!isAnimationPlaying()
|
||||
|
||||
/**
|
||||
* Called when AOD status is changed
|
||||
*/
|
||||
override fun onAlwaysOnChanged(alwaysOn: Boolean) {
|
||||
alwaysOnEnabled = alwaysOn
|
||||
}
|
||||
|
||||
override fun addCallback(listener: FoldAodAnimationStatus) {
|
||||
statusListeners += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: FoldAodAnimationStatus) {
|
||||
statusListeners.remove(listener)
|
||||
}
|
||||
|
||||
interface FoldAodAnimationStatus {
|
||||
fun onFoldToAodAnimationChanged()
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,8 @@ interface SysUIUnfoldComponent {
|
||||
|
||||
fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
|
||||
|
||||
fun getFoldAodAnimationController(): FoldAodAnimationController
|
||||
|
||||
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
|
||||
|
||||
fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
|
||||
|
||||
@@ -264,7 +264,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
|
||||
reset(mView);
|
||||
observer.onChange(true);
|
||||
mExecutor.runAllReady();
|
||||
verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
|
||||
verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
|
||||
}
|
||||
|
||||
private void verifyAttachment(VerificationMode times) {
|
||||
|
||||
@@ -253,8 +253,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingToBigClock_makesSmallClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(LARGE);
|
||||
public void switchingToBigClockWithAnimation_makesSmallClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true);
|
||||
|
||||
mKeyguardClockSwitch.mClockInAnim.end();
|
||||
mKeyguardClockSwitch.mClockOutAnim.end();
|
||||
@@ -265,8 +265,17 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingToSmallClock_makesBigClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(SMALL);
|
||||
public void switchingToBigClockNoAnimation_makesSmallClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false);
|
||||
|
||||
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
|
||||
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
|
||||
assertThat(mClockFrame.getAlpha()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingToSmallClockWithAnimation_makesBigClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true);
|
||||
|
||||
mKeyguardClockSwitch.mClockInAnim.end();
|
||||
mKeyguardClockSwitch.mClockOutAnim.end();
|
||||
@@ -278,9 +287,20 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
|
||||
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
|
||||
mKeyguardClockSwitch.switchToClock(SMALL, false);
|
||||
|
||||
assertThat(mClockFrame.getAlpha()).isEqualTo(1);
|
||||
assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
|
||||
// only big clock is removed at switch
|
||||
assertThat(mLargeClockFrame.getParent()).isNull();
|
||||
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
|
||||
assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
|
||||
assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
|
||||
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
|
||||
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.unfold.FoldAodAnimationController;
|
||||
import com.android.systemui.unfold.SysUIUnfoldComponent;
|
||||
import com.android.systemui.util.wakelock.WakeLockFake;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -54,6 +56,8 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DozeUiTest extends SysuiTestCase {
|
||||
@@ -79,6 +83,10 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private StatusBarStateController mStatusBarStateController;
|
||||
@Mock
|
||||
private FoldAodAnimationController mFoldAodAnimationController;
|
||||
@Mock
|
||||
private SysUIUnfoldComponent mSysUIUnfoldComponent;
|
||||
@Mock
|
||||
private ConfigurationController mConfigurationController;
|
||||
|
||||
@Before
|
||||
@@ -90,9 +98,13 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
mWakeLock = new WakeLockFake();
|
||||
mHandler = mHandlerThread.getThreadHandler();
|
||||
|
||||
when(mSysUIUnfoldComponent.getFoldAodAnimationController())
|
||||
.thenReturn(mFoldAodAnimationController);
|
||||
|
||||
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
|
||||
mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
|
||||
mStatusBarStateController, mConfigurationController);
|
||||
mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
|
||||
mConfigurationController);
|
||||
mDozeUi.setDozeMachine(mMachine);
|
||||
}
|
||||
|
||||
@@ -121,6 +133,7 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
reset(mHost);
|
||||
when(mDozeParameters.getAlwaysOn()).thenReturn(false);
|
||||
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
|
||||
when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
|
||||
|
||||
mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
|
||||
verify(mHost).setAnimateScreenOff(eq(false));
|
||||
@@ -131,6 +144,7 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
reset(mHost);
|
||||
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
|
||||
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
|
||||
when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
|
||||
|
||||
// Take over when the keyguard is visible.
|
||||
mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
|
||||
@@ -141,6 +155,18 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
verify(mHost).setAnimateScreenOff(eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() {
|
||||
reset(mHost);
|
||||
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
|
||||
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
|
||||
when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false);
|
||||
|
||||
// Take over when the keyguard is visible.
|
||||
mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
|
||||
verify(mHost).setAnimateScreenOff(eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neverAnimateScreenOff_whenNotSupported() {
|
||||
// Re-initialize DozeParameters saying that the display requires blanking.
|
||||
@@ -149,7 +175,8 @@ public class DozeUiTest extends SysuiTestCase {
|
||||
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
|
||||
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
|
||||
mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
|
||||
mStatusBarStateController, mConfigurationController);
|
||||
mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
|
||||
mConfigurationController);
|
||||
mDozeUi.setDozeMachine(mMachine);
|
||||
|
||||
// Never animate if display doesn't support it.
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.unfold.FoldAodAnimationController;
|
||||
import com.android.systemui.unfold.SysUIUnfoldComponent;
|
||||
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
@@ -69,7 +70,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -95,62 +95,40 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
private @Mock NavigationModeController mNavigationModeController;
|
||||
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
|
||||
private @Mock DozeParameters mDozeParameters;
|
||||
private @Mock Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent;
|
||||
private @Mock Optional<UnfoldLightRevealOverlayAnimation> mUnfoldAnimationOptional;
|
||||
private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent;
|
||||
private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
|
||||
private @Mock SysuiStatusBarStateController mStatusBarStateController;
|
||||
private @Mock KeyguardStateController mKeyguardStateController;
|
||||
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
|
||||
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
|
||||
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
|
||||
private @Mock FoldAodAnimationController mFoldAodAnimationController;
|
||||
private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
|
||||
private @Mock InteractionJankMonitor mInteractionJankMonitor;
|
||||
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
|
||||
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional;
|
||||
|
||||
private FalsingCollectorFake mFalsingCollector;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mFalsingCollector = new FalsingCollectorFake();
|
||||
mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent);
|
||||
|
||||
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
|
||||
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
|
||||
when(mSysUIUnfoldComponent.map(
|
||||
ArgumentMatchers.<Function<SysUIUnfoldComponent, UnfoldLightRevealOverlayAnimation>>
|
||||
any()))
|
||||
.thenReturn(mUnfoldAnimationOptional);
|
||||
when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
|
||||
when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
|
||||
when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation())
|
||||
.thenReturn(mUnfoldAnimation);
|
||||
when(mSysUIUnfoldComponent.getFoldAodAnimationController())
|
||||
.thenReturn(mFoldAodAnimationController);
|
||||
|
||||
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
|
||||
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
|
||||
|
||||
mViewMediator = new KeyguardViewMediator(
|
||||
mContext,
|
||||
mFalsingCollector,
|
||||
mLockPatternUtils,
|
||||
mBroadcastDispatcher,
|
||||
() -> mStatusBarKeyguardViewManager,
|
||||
mDismissCallbackRegistry,
|
||||
mUpdateMonitor,
|
||||
mDumpManager,
|
||||
mUiBgExecutor,
|
||||
mPowerManager,
|
||||
mTrustManager,
|
||||
mUserSwitcherController,
|
||||
mDeviceConfig,
|
||||
mNavigationModeController,
|
||||
mKeyguardDisplayManager,
|
||||
mDozeParameters,
|
||||
mSysUIUnfoldComponent,
|
||||
mStatusBarStateController,
|
||||
mKeyguardStateController,
|
||||
() -> mKeyguardUnlockAnimationController,
|
||||
mScreenOffAnimationController,
|
||||
() -> mNotificationShadeDepthController,
|
||||
mInteractionJankMonitor);
|
||||
mViewMediator.start();
|
||||
createAndStartViewMediator();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,6 +157,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
onUnfoldOverlayReady();
|
||||
onFoldAodReady();
|
||||
|
||||
// Should be called when both unfold overlay and keyguard drawn ready
|
||||
verify(mKeyguardDrawnCallback).onDrawn();
|
||||
@@ -188,7 +167,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
|
||||
throws RemoteException {
|
||||
when(mUnfoldAnimationOptional.isPresent()).thenReturn(false);
|
||||
mSysUiUnfoldComponentOptional = Optional.empty();
|
||||
createAndStartViewMediator();
|
||||
|
||||
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
@@ -200,6 +180,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testIsAnimatingScreenOff() {
|
||||
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
|
||||
when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
|
||||
|
||||
mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
|
||||
mViewMediator.setDozing(true);
|
||||
@@ -244,4 +225,39 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
overlayReadyCaptor.getValue().run();
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
}
|
||||
|
||||
private void onFoldAodReady() {
|
||||
ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture());
|
||||
ready.getValue().run();
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
}
|
||||
|
||||
private void createAndStartViewMediator() {
|
||||
mViewMediator = new KeyguardViewMediator(
|
||||
mContext,
|
||||
mFalsingCollector,
|
||||
mLockPatternUtils,
|
||||
mBroadcastDispatcher,
|
||||
() -> mStatusBarKeyguardViewManager,
|
||||
mDismissCallbackRegistry,
|
||||
mUpdateMonitor,
|
||||
mDumpManager,
|
||||
mUiBgExecutor,
|
||||
mPowerManager,
|
||||
mTrustManager,
|
||||
mUserSwitcherController,
|
||||
mDeviceConfig,
|
||||
mNavigationModeController,
|
||||
mKeyguardDisplayManager,
|
||||
mDozeParameters,
|
||||
mSysUiUnfoldComponentOptional,
|
||||
mStatusBarStateController,
|
||||
mKeyguardStateController,
|
||||
() -> mKeyguardUnlockAnimationController,
|
||||
mScreenOffAnimationController,
|
||||
() -> mNotificationShadeDepthController,
|
||||
mInteractionJankMonitor);
|
||||
mViewMediator.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +424,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
.thenReturn(mKeyguardUserSwitcherComponent);
|
||||
when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
|
||||
.thenReturn(mKeyguardUserSwitcherController);
|
||||
when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
|
||||
|
||||
doAnswer((Answer<Void>) invocation -> {
|
||||
mTouchHandler = invocation.getArgument(0);
|
||||
@@ -879,11 +880,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
|
||||
triggerPositionClockAndNotifications();
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE);
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
|
||||
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
mNotificationPanelViewController.closeQs();
|
||||
verify(mKeyguardStatusViewController).displayClock(SMALL);
|
||||
verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -893,12 +894,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
|
||||
triggerPositionClockAndNotifications();
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE);
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
|
||||
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
triggerPositionClockAndNotifications();
|
||||
verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
|
||||
verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
|
||||
verify(mKeyguardStatusViewController, times(2))
|
||||
.displayClock(LARGE, /* animate */ true);
|
||||
verify(mKeyguardStatusViewController, never())
|
||||
.displayClock(SMALL, /* animate */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -910,7 +913,20 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
|
||||
mNotificationPanelViewController.setDozing(true, false, null);
|
||||
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE);
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
|
||||
when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
|
||||
mStatusBarStateController.setState(KEYGUARD);
|
||||
enableSplitShade(/* enabled= */ true);
|
||||
when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
|
||||
|
||||
mNotificationPanelViewController.setDozing(true, false, null);
|
||||
|
||||
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -922,13 +938,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
// one notification + media player visible
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
triggerPositionClockAndNotifications();
|
||||
verify(mKeyguardStatusViewController).displayClock(SMALL);
|
||||
verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
|
||||
|
||||
// only media player visible
|
||||
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
|
||||
triggerPositionClockAndNotifications();
|
||||
verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
|
||||
verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
|
||||
verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
|
||||
verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user