Merge "Implement dream out overlay transition" into tm-qpr-dev

This commit is contained in:
Lucas Silva
2022-11-15 19:49:26 +00:00
committed by Android (Google) Code Review
23 changed files with 704 additions and 171 deletions

View File

@@ -49,6 +49,17 @@ public abstract class DreamOverlayService extends Service {
mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
@Override
public void wakeUp() {
onWakeUp(() -> {
try {
mDreamOverlayCallback.onWakeUpComplete();
} catch (RemoteException e) {
Log.e(TAG, "Could not notify dream of wakeUp:" + e);
}
});
}
};
IDreamOverlayCallback mDreamOverlayCallback;
@@ -70,6 +81,17 @@ public abstract class DreamOverlayService extends Service {
*/
public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
/**
* This method is overridden by implementations to handle when the dream has been requested
* to wakeup. This allows any overlay animations to run.
*
* @param onCompleteCallback The callback to trigger to notify the dream service that the
* overlay has completed waking up.
* @hide
*/
public void onWakeUp(@NonNull Runnable onCompleteCallback) {
}
/**
* This method is invoked to request the dream exit.
*/

View File

@@ -312,7 +312,14 @@ public class DreamService extends Service implements Window.Callback {
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
finish();
mHandler.post(() -> finish());
}
@Override
public void onWakeUpComplete() {
// Finish the dream once overlay animations are complete. Execute on handler since
// this is coming in on the overlay binder.
mHandler.post(() -> finish());
}
};
@@ -975,7 +982,18 @@ public class DreamService extends Service implements Window.Callback {
* </p>
*/
public void onWakeUp() {
finish();
if (mOverlayConnection != null) {
mOverlayConnection.addConsumer(overlay -> {
try {
overlay.wakeUp();
} catch (RemoteException e) {
Slog.e(TAG, "Error waking the overlay service", e);
finish();
}
});
} else {
finish();
}
}
/** {@inheritDoc} */

View File

@@ -38,4 +38,7 @@ interface IDreamOverlay {
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
/** Called when the dream is waking, to do any exit animations */
void wakeUp();
}

View File

@@ -28,4 +28,7 @@ interface IDreamOverlayCallback {
* Invoked to request the dream exit.
*/
void onExitRequested();
/** Invoked when the dream overlay wakeUp animation is complete. */
void onWakeUpComplete();
}

View File

@@ -2485,7 +2485,7 @@
<!-- The duration in milliseconds of the dream opening animation. -->
<integer name="config_dreamOpenAnimationDuration">250</integer>
<!-- The duration in milliseconds of the dream closing animation. -->
<integer name="config_dreamCloseAnimationDuration">100</integer>
<integer name="config_dreamCloseAnimationDuration">300</integer>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->

View File

@@ -743,12 +743,35 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
<!-- The duration in milliseconds of the y-translation animation when waking up from
the dream -->
<integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
<!-- The delay in milliseconds of the y-translation animation when waking up from
the dream for the complications at the bottom of the screen -->
<integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
<!-- The delay in milliseconds of the y-translation animation when waking up from
the dream for the complications at the top of the screen -->
<integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
<!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
<integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
<!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
complications at the top of the screen -->
<integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
<!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
complications at the bottom of the screen -->
<integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
<!-- The duration in milliseconds of the blur animation when waking up from
the dream -->
<integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>
<integer name="complicationRestoreMs">1000</integer>
<integer name="complicationFadeOutDelayMs">200</integer>
<!-- Duration in milliseconds of the dream in un-blur animation. -->
<integer name="config_dreamOverlayInBlurDurationMs">249</integer>
<!-- Delay in milliseconds of the dream in un-blur animation. -->
@@ -780,27 +803,6 @@
<item>com.android.systemui</item>
</string-array>
<!-- The thresholds which determine the color used by the AQI dream overlay.
NOTE: This must always be kept sorted from low to high -->
<integer-array name="config_dreamAqiThresholds">
<item>-1</item>
<item>50</item>
<item>100</item>
<item>150</item>
<item>200</item>
<item>300</item>
</integer-array>
<!-- The color values which correspond to the thresholds above -->
<integer-array name="config_dreamAqiColorValues">
<item>@color/dream_overlay_aqi_good</item>
<item>@color/dream_overlay_aqi_moderate</item>
<item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
<item>@color/dream_overlay_aqi_unhealthy</item>
<item>@color/dream_overlay_aqi_very_unhealthy</item>
<item>@color/dream_overlay_aqi_hazardous</item>
</integer-array>
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>

View File

@@ -1534,6 +1534,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
<dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>

View File

@@ -17,17 +17,19 @@
package com.android.systemui.dreams
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
import java.util.function.Consumer
import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
@@ -40,108 +42,239 @@ constructor(
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDuration: Int,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
private val mDreamInComplicationsAnimDuration: Int,
private val mDreamInComplicationsAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
private val mDreamInTopComplicationsAnimDelay: Int,
private val mDreamInTopComplicationsAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
private val mDreamInBottomComplicationsAnimDelay: Int
private val mDreamInBottomComplicationsAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
private val mDreamOutTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
private val mDreamOutTranslationYDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
private val mDreamOutTranslationYDelayBottomMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
private val mDreamOutTranslationYDelayTopMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
private val mDreamOutAlphaDelayBottomMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
var mEntryAnimations: AnimatorSet? = null
private var mAnimator: Animator? = null
/**
* Store the current alphas at the various positions. This is so that we may resume an animation
* at the current alpha.
*/
private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
@FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
fun startEntryAnimations(view: View) {
cancelRunningEntryAnimations()
@JvmOverloads
fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
cancelAnimations()
mEntryAnimations = AnimatorSet()
mEntryAnimations?.apply {
playTogether(
buildDreamInBlurAnimator(view),
buildDreamInTopComplicationsAnimator(),
buildDreamInBottomComplicationsAnimator()
)
doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
start()
}
mAnimator =
animatorBuilder().apply {
playTogether(
blurAnimator(
view = view,
from = 1f,
to = 0f,
durationMs = mDreamInBlurAnimDurationMs,
delayMs = mDreamInBlurAnimDelayMs
),
alphaAnimator(
from = 0f,
to = 1f,
durationMs = mDreamInComplicationsAnimDurationMs,
delayMs = mDreamInTopComplicationsAnimDelayMs,
position = ComplicationLayoutParams.POSITION_TOP
),
alphaAnimator(
from = 0f,
to = 1f,
durationMs = mDreamInComplicationsAnimDurationMs,
delayMs = mDreamInBottomComplicationsAnimDelayMs,
position = ComplicationLayoutParams.POSITION_BOTTOM
)
)
doOnEnd {
mAnimator = null
mOverlayStateController.setEntryAnimationsFinished(true)
}
start()
}
}
/** Starts the dream content and dream overlay exit animations. */
@JvmOverloads
fun startExitAnimations(
view: View,
doneCallback: () -> Unit,
animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
) {
cancelAnimations()
mAnimator =
animatorBuilder().apply {
playTogether(
blurAnimator(
view = view,
// Start the blurring wherever the entry animation ended, in
// case it was cancelled early.
from = mBlurProgress,
to = 1f,
durationMs = mDreamOutBlurDurationMs
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayBottomMs,
position = ComplicationLayoutParams.POSITION_BOTTOM,
animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayTopMs,
position = ComplicationLayoutParams.POSITION_TOP,
animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = ComplicationLayoutParams.POSITION_BOTTOM,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayBottomMs,
position = ComplicationLayoutParams.POSITION_BOTTOM
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = ComplicationLayoutParams.POSITION_TOP,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayTopMs,
position = ComplicationLayoutParams.POSITION_TOP
)
)
doOnEnd {
mAnimator = null
mOverlayStateController.setExitAnimationsRunning(false)
doneCallback()
}
start()
}
mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
fun cancelRunningEntryAnimations() {
if (mEntryAnimations?.isRunning == true) {
mEntryAnimations?.cancel()
}
mEntryAnimations = null
fun cancelAnimations() {
mAnimator =
mAnimator?.let {
it.cancel()
null
}
}
private fun buildDreamInBlurAnimator(view: View): Animator {
return ValueAnimator.ofFloat(1f, 0f).apply {
duration = mDreamInBlurAnimDuration.toLong()
startDelay = mDreamInBlurAnimDelay.toLong()
private fun blurAnimator(
view: View,
from: Float,
to: Float,
durationMs: Long,
delayMs: Long = 0
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { animator: ValueAnimator ->
mBlurProgress = animator.animatedValue as Float
mBlurUtils.applyBlur(
view.viewRootImpl,
mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
false /*opaque*/
viewRootImpl = view.viewRootImpl,
radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
opaque = false
)
}
}
}
private fun buildDreamInTopComplicationsAnimator(): Animator {
return ValueAnimator.ofFloat(0f, 1f).apply {
duration = mDreamInComplicationsAnimDuration.toLong()
startDelay = mDreamInTopComplicationsAnimDelay.toLong()
private fun alphaAnimator(
from: Float,
to: Float,
durationMs: Long,
delayMs: Long,
@Position position: Int
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { va: ValueAnimator ->
setTopElementsAlpha(va.animatedValue as Float)
setElementsAlphaAtPosition(
alpha = va.animatedValue as Float,
position = position,
fadingOut = to < from
)
}
}
}
private fun buildDreamInBottomComplicationsAnimator(): Animator {
return ValueAnimator.ofFloat(0f, 1f).apply {
duration = mDreamInComplicationsAnimDuration.toLong()
startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
interpolator = Interpolators.LINEAR
private fun translationYAnimator(
from: Float,
to: Float,
durationMs: Long,
delayMs: Long,
@Position position: Int,
animInterpolator: Interpolator
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
interpolator = animInterpolator
addUpdateListener { va: ValueAnimator ->
setBottomElementsAlpha(va.animatedValue as Float)
setElementsTranslationYAtPosition(va.animatedValue as Float, position)
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
mComplicationHostViewController
.getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
.forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
}
}
)
}
}
/** Sets alpha of top complications and the status bar. */
private fun setTopElementsAlpha(alpha: Float) {
mComplicationHostViewController
.getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
.forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
mStatusBarViewController.setAlpha(alpha)
}
/** Sets alpha of bottom complications. */
private fun setBottomElementsAlpha(alpha: Float) {
mComplicationHostViewController
.getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
.forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
}
private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
if (alpha > 0 && view.visibility != View.VISIBLE) {
view.visibility = View.VISIBLE
/** Sets alpha of complications at the specified position. */
private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
mCurrentAlphaAtPosition[position] = alpha
mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
if (fadingOut) {
CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
} else {
CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
}
}
if (position == ComplicationLayoutParams.POSITION_TOP) {
mStatusBarViewController.setFadeAmount(alpha, fadingOut)
}
}
view.alpha = alpha
/** Sets y translation of complications at the specified position. */
private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
v.translationY = translationY
}
if (position == ComplicationLayoutParams.POSITION_TOP) {
mStatusBarViewController.setTranslationY(translationY)
}
}
}

View File

@@ -29,6 +29,8 @@ import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -42,6 +44,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -194,7 +197,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
}
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
mDreamOverlayAnimationsController.cancelAnimations();
}
View getContainerView() {
@@ -251,4 +254,17 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
: aboutToShowBouncerProgress(expansion + 0.03f));
return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
}
/**
* Handle the dream waking up and run any necessary animations.
*
* @param onAnimationEnd Callback to trigger once animations are finished.
* @param callbackExecutor Executor to execute the callback on.
*/
public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
callbackExecutor.execute(onAnimationEnd);
return null;
});
}
}

View File

@@ -213,6 +213,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mLifecycleRegistry.setCurrentState(state);
}
@Override
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) {
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
}
});
}
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are

View File

@@ -52,6 +52,7 @@ public class DreamOverlayStateController implements
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -211,6 +212,14 @@ public class DreamOverlayStateController implements
return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
}
/**
* Returns whether the dream content and dream overlay exit animations are running.
* @return {@code true} if animations are running, {@code false} otherwise.
*/
public boolean areExitAnimationsRunning() {
return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -256,6 +265,15 @@ public class DreamOverlayStateController implements
STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
}
/**
* Sets whether dream content and dream overlay exit animations are running.
* @param running {@code true} if exit animations are running, {@code false} otherwise.
*/
public void setExitAnimationsRunning(boolean running) {
modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
/**
* Returns the available complication types.
*/

View File

@@ -37,6 +37,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -217,18 +218,29 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
}
/**
* Sets alpha of the dream overlay status bar.
* Sets fade of the dream overlay status bar.
*
* No-op if the dream overlay status bar should not be shown.
*/
protected void setAlpha(float alpha) {
protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
updateVisibility();
if (mView.getVisibility() != View.VISIBLE) {
return;
}
mView.setAlpha(alpha);
if (fadingOut) {
CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
} else {
CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
}
}
/**
* Sets the y translation of the dream overlay status bar.
*/
public void setTranslationY(float translationY) {
mView.setTranslationY(translationY);
}
private boolean shouldShowStatusBar() {

View File

@@ -197,11 +197,11 @@ public interface Complication {
*/
interface VisibilityController {
/**
* Called to set the visibility of all shown and future complications.
* Called to set the visibility of all shown and future complications. Changes in visibility
* will always be animated.
* @param visibility The desired future visibility.
* @param animate whether the change should be animated.
*/
void setVisibility(@View.Visibility int visibility, boolean animate);
void setVisibility(@View.Visibility int visibility);
}
/**

View File

@@ -21,12 +21,9 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationHostVi
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@ import androidx.constraintlayout.widget.Constraints;
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
import java.util.ArrayList;
@@ -481,7 +479,6 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
private final TouchInsetManager.TouchInsetSession mSession;
private final int mFadeInDuration;
private final int mFadeOutDuration;
private ViewPropertyAnimator mViewPropertyAnimator;
/** */
@Inject
@@ -498,26 +495,16 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
}
@Override
public void setVisibility(int visibility, boolean animate) {
final boolean appearing = visibility == View.VISIBLE;
if (mViewPropertyAnimator != null) {
mViewPropertyAnimator.cancel();
public void setVisibility(int visibility) {
if (visibility == View.VISIBLE) {
CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
} else {
CrossFadeHelper.fadeOut(
mLayout,
mFadeOutDuration,
/* delay= */ 0,
/* endRunnable= */ null);
}
if (appearing) {
mLayout.setVisibility(View.VISIBLE);
}
mViewPropertyAnimator = mLayout.animate()
.alpha(appearing ? 1f : 0f)
.setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLayout.setVisibility(visibility);
}
});
}
/**

View File

@@ -38,7 +38,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
POSITION_START,
})
@interface Position {}
public @interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */

View File

@@ -41,6 +41,7 @@ public abstract class ComplicationHostViewModule {
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
/**
* Generates a {@link ConstraintLayout}, which can host
@@ -74,6 +75,16 @@ public abstract class ComplicationHostViewModule {
return resources.getInteger(R.integer.complicationFadeOutMs);
}
/**
* Provides the delay to wait for before fading out complications.
*/
@Provides
@Named(COMPLICATIONS_FADE_OUT_DELAY)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeOutDelay(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeOutDelayMs);
}
/**
* Provides the fade in duration for complications.
*/

View File

@@ -55,6 +55,22 @@ public abstract class DreamOverlayModule {
"dream_in_top_complications_anim_delay";
public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
"dream_in_bottom_complications_anim_delay";
public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
"dream_out_complications_translation_y";
public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
"dream_out_complications_translation_y_duration";
public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
"dream_out_complications_translation_y_delay_bottom";
public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
"dream_out_complications_translation_y_delay_top";
public static final String DREAM_OUT_ALPHA_DURATION =
"dream_out_complications_alpha_duration";
public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
"dream_out_complications_alpha_delay_bottom";
public static final String DREAM_OUT_ALPHA_DELAY_TOP =
"dream_out_complications_alpha_delay_top";
public static final String DREAM_OUT_BLUR_DURATION =
"dream_out_blur_duration";
/** */
@Provides
@@ -127,8 +143,8 @@ public abstract class DreamOverlayModule {
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DURATION)
static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
}
/**
@@ -136,8 +152,8 @@ public abstract class DreamOverlayModule {
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DELAY)
static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
}
/**
@@ -145,8 +161,8 @@ public abstract class DreamOverlayModule {
*/
@Provides
@Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
}
/**
@@ -154,8 +170,8 @@ public abstract class DreamOverlayModule {
*/
@Provides
@Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
}
/**
@@ -163,8 +179,69 @@ public abstract class DreamOverlayModule {
*/
@Provides
@Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
return (long) resources.getInteger(
R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
}
/**
* Provides the number of pixels to translate complications when waking up from dream.
*/
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
@DreamOverlayComponent.DreamOverlayScope
static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
return (long) resources.getInteger(
R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DELAY_TOP)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
}
@Provides
@Named(DREAM_OUT_BLUR_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutBlurDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
}
@Provides

View File

@@ -16,22 +16,26 @@
package com.android.systemui.dreams.touch;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayDeque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -49,33 +53,58 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Complication.VisibilityController mVisibilityController;
private final int mRestoreTimeout;
private final int mFadeOutDelay;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final Handler mHandler;
private final Executor mExecutor;
private final DelayableExecutor mExecutor;
private final DreamOverlayStateController mOverlayStateController;
private final TouchInsetManager mTouchInsetManager;
private final Complication.VisibilityController mVisibilityController;
private boolean mHidden = false;
@Nullable
private Runnable mHiddenCallback;
private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
private final Runnable mRestoreComplications = new Runnable() {
@Override
public void run() {
mVisibilityController.setVisibility(View.VISIBLE, true);
mVisibilityController.setVisibility(View.VISIBLE);
mHidden = false;
}
};
private final Runnable mHideComplications = new Runnable() {
@Override
public void run() {
if (mOverlayStateController.areExitAnimationsRunning()) {
// Avoid interfering with the exit animations.
return;
}
mVisibilityController.setVisibility(View.INVISIBLE);
mHidden = true;
if (mHiddenCallback != null) {
mHiddenCallback.run();
mHiddenCallback = null;
}
}
};
@Inject
HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
@Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
@Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
TouchInsetManager touchInsetManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@Main Executor executor,
@Main Handler handler) {
@Main DelayableExecutor executor,
DreamOverlayStateController overlayStateController) {
mVisibilityController = visibilityController;
mRestoreTimeout = restoreTimeout;
mFadeOutDelay = fadeOutDelay;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mHandler = handler;
mTouchInsetManager = touchInsetManager;
mExecutor = executor;
mOverlayStateController = overlayStateController;
}
@Override
@@ -87,7 +116,8 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
// If other sessions are interested in this touch, do not fade out elements.
if (session.getActiveSessionCount() > 1 || bouncerShowing) {
if (session.getActiveSessionCount() > 1 || bouncerShowing
|| mOverlayStateController.areExitAnimationsRunning()) {
if (DEBUG) {
Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
+ ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
touchCheck.addListener(() -> {
try {
if (!touchCheck.get()) {
mHandler.removeCallbacks(mRestoreComplications);
mVisibilityController.setVisibility(View.INVISIBLE, true);
// Cancel all pending callbacks.
while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
mCancelCallbacks.add(
mExecutor.executeDelayed(
mHideComplications, mFadeOutDelay));
} else {
// If a touch occurred inside the dream overlay touch insets, do not
// handle the touch.
@@ -130,7 +163,23 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
|| motionEvent.getAction() == MotionEvent.ACTION_UP) {
// End session and initiate delayed reappearance of the complications.
session.pop();
mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
runAfterHidden(() -> mCancelCallbacks.add(
mExecutor.executeDelayed(mRestoreComplications,
mRestoreTimeout)));
}
});
}
/**
* Triggers a runnable after complications have been hidden. Will override any previously set
* runnable currently waiting for hide to happen.
*/
private void runAfterHidden(Runnable runnable) {
mExecutor.execute(() -> {
if (mHidden) {
runnable.run();
} else {
mHiddenCallback = runnable;
}
});
}

View File

@@ -408,6 +408,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
private final int mDreamOpenAnimationDuration;
/**
* The duration in milliseconds of the dream close animation.
*/
private final int mDreamCloseAnimationDuration;
/**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
@@ -1054,7 +1059,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
: UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
mUnoccludeAnimator.addUpdateListener(
animation -> {
@@ -1204,6 +1210,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
mDreamCloseAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamCloseAnimationDuration);
}
public void userActivity() {

View File

@@ -0,0 +1,125 @@
package com.android.systemui.dreams
import android.animation.Animator
import android.animation.AnimatorSet
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
companion object {
private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
private const val DREAM_OUT_ALPHA_DURATION = 10L
private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
private const val DREAM_OUT_BLUR_DURATION = 13L
}
@Mock private lateinit var mockAnimator: AnimatorSet
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
private lateinit var controller: DreamOverlayAnimationsController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
controller =
DreamOverlayAnimationsController(
blurUtils,
hostViewController,
statusBarViewController,
stateController,
DREAM_IN_BLUR_ANIMATION_DURATION,
DREAM_IN_BLUR_ANIMATION_DELAY,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
DREAM_OUT_TRANSLATION_Y_DISTANCE,
DREAM_OUT_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
DREAM_OUT_ALPHA_DURATION,
DREAM_OUT_ALPHA_DELAY_BOTTOM,
DREAM_OUT_ALPHA_DELAY_TOP,
DREAM_OUT_BLUR_DURATION
)
}
@Test
fun testExitAnimationOnEnd() {
val mockCallback: () -> Unit = mock()
controller.startExitAnimations(
view = mock(),
doneCallback = mockCallback,
animatorBuilder = { mockAnimator }
)
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator).addListener(captor.capture())
val listener = captor.value
verify(mockCallback, never()).invoke()
listener.onAnimationEnd(mockAnimator)
verify(mockCallback, times(1)).invoke()
}
@Test
fun testCancellation() {
controller.startExitAnimations(
view = mock(),
doneCallback = mock(),
animatorBuilder = { mockAnimator }
)
verify(mockAnimator, never()).cancel()
controller.cancelAnimations()
verify(mockAnimator, times(1)).cancel()
}
@Test
fun testExitAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
val mockExitAnimator: AnimatorSet = mock()
controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
controller.startExitAnimations(
view = mock(),
doneCallback = mock(),
animatorBuilder = { mockExitAnimator }
)
// Verify that we cancelled the start animator in favor of the exit
// animator.
verify(mockStartAnimator, times(1)).cancel()
verify(mockExitAnimator, never()).cancel()
}
}

View File

@@ -204,7 +204,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
verify(mAnimationsController, never()).cancelRunningEntryAnimations();
verify(mAnimationsController, never()).cancelAnimations();
}
@Test
@@ -221,6 +221,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
mController.onViewDetached();
verify(mAnimationsController).cancelRunningEntryAnimations();
verify(mAnimationsController).cancelAnimations();
}
}

View File

@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -337,4 +338,28 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
}
@Test
public void testWakeUp() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
final Runnable callback = mock(Runnable.class);
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
}
@Test
public void testWakeUpBeforeStartDoesNothing() {
final Runnable callback = mock(Runnable.class);
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
}
}

View File

@@ -16,8 +16,6 @@
package com.android.systemui.dreams.touch;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -33,6 +31,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class HideComplicationTouchHandlerTest extends SysuiTestCase {
private static final int RESTORE_TIMEOUT = 1000;
private static final int HIDE_DELAY = 500;
@Mock
Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
@Mock
DreamTouchHandler.TouchSession mSession;
FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Mock
DreamOverlayStateController mStateController;
FakeSystemClock mClock;
FakeExecutor mFakeExecutor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mClock = new FakeSystemClock();
mFakeExecutor = new FakeExecutor(mClock);
}
/**
@@ -86,10 +93,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
mStateController);
// Report multiple active sessions.
when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
// Verify session end.
verify(mSession).pop();
mClock.advanceTime(HIDE_DELAY);
// Verify no interaction with visibility controller.
verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -115,10 +125,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
mStateController);
// Report one session.
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
// Verify session end.
verify(mSession).pop();
mClock.advanceTime(HIDE_DELAY);
// Verify no interaction with visibility controller.
verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -144,10 +157,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
// Verify session ended.
verify(mSession).pop();
mClock.advanceTime(HIDE_DELAY);
// Verify no interaction with visibility controller.
verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -189,10 +205,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
mFakeExecutor.runAllReady();
// Verify callback to restore visibility cancelled.
verify(mHandler).removeCallbacks(any());
// Verify visibility controller doesn't hide until after timeout
verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
mClock.advanceTime(HIDE_DELAY);
// Verify visibility controller told to hide complications.
verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
Mockito.clearInvocations(mVisibilityController, mHandler);
@@ -235,11 +252,8 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
mFakeExecutor.runAllReady();
// Verify visibility controller told to show complications.
ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
eq(Long.valueOf(RESTORE_TIMEOUT)));
delayRunnableCaptor.getValue().run();
verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
mClock.advanceTime(RESTORE_TIMEOUT);
verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
// Verify session ended.
verify(mSession).pop();