blob: d7b7777559da702fa00fbfb7fa531f2362f209a7 [file] [log] [blame]
/*
* 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.dreams;
import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
import android.content.res.Resources;
import android.os.Handler;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Named;
/**
* View controller for {@link DreamOverlayContainerView}.
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final BlurUtils mBlurUtils;
private final ComplicationHostViewController mComplicationHostViewController;
// The dream overlay's content view, which is located below the status bar (in z-order) and is
// the space into which widgets are placed.
private final ViewGroup mDreamOverlayContentView;
// The maximum translation offset to apply to the overlay container to avoid screen burn-in.
private final int mMaxBurnInOffset;
// The interval in milliseconds between burn-in protection updates.
private final long mBurnInProtectionUpdateInterval;
// Amount of time in milliseconds to linear interpolate toward the final jitter offset. Once
// this time is achieved, the normal jitter algorithm applies in full.
private final long mMillisUntilFullJitter;
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
private final int mDreamOverlayMaxTranslationY;
private long mJitterStartTimeMillis;
private boolean mBouncerAnimating;
private final KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback =
new KeyguardBouncer.BouncerExpansionCallback() {
@Override
public void onStartingToShow() {
mBouncerAnimating = true;
}
@Override
public void onStartingToHide() {
mBouncerAnimating = true;
}
@Override
public void onFullyHidden() {
mBouncerAnimating = false;
}
@Override
public void onFullyShown() {
mBouncerAnimating = false;
}
@Override
public void onExpansionChanged(float bouncerHideAmount) {
if (mBouncerAnimating) {
updateTransitionState(bouncerHideAmount);
}
}
@Override
public void onVisibilityChanged(boolean isVisible) {
// The bouncer may be hidden abruptly without triggering onExpansionChanged.
// In this case, we should reset the transition state.
if (!isVisible) {
updateTransitionState(1f);
}
}
};
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mBlurUtils = blurUtils;
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
R.dimen.dream_overlay_y_offset);
final View view = mComplicationHostViewController.getView();
mDreamOverlayContentView.addView(view,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mHandler = handler;
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
}
@Override
protected void onInit() {
mStatusBarViewController.init();
mComplicationHostViewController.init();
}
@Override
protected void onViewAttached() {
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
if (bouncer != null) {
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
}
@Override
protected void onViewDetached() {
mHandler.removeCallbacks(this::updateBurnInOffsets);
final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
if (bouncer != null) {
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
}
View getContainerView() {
return mView;
}
private void updateBurnInOffsets() {
// Make sure the offset starts at zero, to avoid a big jump in the overlay when it first
// appears.
final long millisSinceStart = System.currentTimeMillis() - mJitterStartTimeMillis;
final int burnInOffset;
if (millisSinceStart < mMillisUntilFullJitter) {
float lerpAmount = (float) millisSinceStart / (float) mMillisUntilFullJitter;
burnInOffset = Math.round(MathUtils.lerp(0f, mMaxBurnInOffset, lerpAmount));
} else {
burnInOffset = mMaxBurnInOffset;
}
// These translation values change slowly, and the set translation methods are idempotent,
// so no translation occurs when the values don't change.
final int halfBurnInOffset = burnInOffset / 2;
final int burnInOffsetX = getBurnInOffset(burnInOffset, true) - halfBurnInOffset;
final int burnInOffsetY = getBurnInOffset(burnInOffset, false) - halfBurnInOffset;
mView.setTranslationX(burnInOffsetX);
mView.setTranslationY(burnInOffsetY);
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
}
private void updateTransitionState(float bouncerHideAmount) {
for (int position : Arrays.asList(POSITION_TOP, POSITION_BOTTOM)) {
final float alpha = getAlpha(position, bouncerHideAmount);
final float translationY = getTranslationY(position, bouncerHideAmount);
mComplicationHostViewController.getViewsAtPosition(position).forEach(v -> {
v.setAlpha(alpha);
v.setTranslationY(translationY);
});
}
mBlurUtils.applyBlur(mView.getViewRootImpl(),
(int) mBlurUtils.blurRadiusOfRatio(
1 - aboutToShowBouncerProgress(bouncerHideAmount)), false);
}
private static float getAlpha(int position, float expansion) {
return Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
position == POSITION_TOP ? getDreamAlphaScaledExpansion(expansion)
: aboutToShowBouncerProgress(expansion + 0.03f));
}
private float getTranslationY(int position, float expansion) {
final float fraction = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
position == POSITION_TOP ? getDreamYPositionScaledExpansion(expansion)
: aboutToShowBouncerProgress(expansion + 0.03f));
return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
}
}