1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.statehandlers; 18 19 import static com.android.launcher3.anim.Interpolators.LINEAR; 20 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; 21 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ObjectAnimator; 26 import android.os.IBinder; 27 import android.os.SystemProperties; 28 import android.util.FloatProperty; 29 import android.view.CrossWindowBlurListeners; 30 import android.view.SurfaceControl; 31 import android.view.View; 32 import android.view.ViewRootImpl; 33 import android.view.ViewTreeObserver; 34 35 import com.android.launcher3.BaseActivity; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.LauncherState; 38 import com.android.launcher3.R; 39 import com.android.launcher3.Utilities; 40 import com.android.launcher3.anim.PendingAnimation; 41 import com.android.launcher3.statemanager.StateManager.StateHandler; 42 import com.android.launcher3.states.StateAnimationConfig; 43 import com.android.systemui.shared.system.BlurUtils; 44 import com.android.systemui.shared.system.WallpaperManagerCompat; 45 46 import java.util.function.Consumer; 47 48 /** 49 * Controls blur and wallpaper zoom, for the Launcher surface only. 50 */ 51 public class DepthController implements StateHandler<LauncherState>, 52 BaseActivity.MultiWindowModeChangedListener { 53 54 public static final FloatProperty<DepthController> DEPTH = 55 new FloatProperty<DepthController>("depth") { 56 @Override 57 public void setValue(DepthController depthController, float depth) { 58 depthController.setDepth(depth); 59 } 60 61 @Override 62 public Float get(DepthController depthController) { 63 return depthController.mDepth; 64 } 65 }; 66 67 /** 68 * A property that updates the background blur within a given range of values (ie. even if the 69 * animator goes beyond 0..1, the interpolated value will still be bounded). 70 */ 71 public static class ClampedDepthProperty extends FloatProperty<DepthController> { 72 private final float mMinValue; 73 private final float mMaxValue; 74 ClampedDepthProperty(float minValue, float maxValue)75 public ClampedDepthProperty(float minValue, float maxValue) { 76 super("depthClamped"); 77 mMinValue = minValue; 78 mMaxValue = maxValue; 79 } 80 81 @Override setValue(DepthController depthController, float depth)82 public void setValue(DepthController depthController, float depth) { 83 depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue)); 84 } 85 86 @Override get(DepthController depthController)87 public Float get(DepthController depthController) { 88 return depthController.mDepth; 89 } 90 } 91 92 private final ViewTreeObserver.OnDrawListener mOnDrawListener = 93 new ViewTreeObserver.OnDrawListener() { 94 @Override 95 public void onDraw() { 96 View view = mLauncher.getDragLayer(); 97 ViewRootImpl viewRootImpl = view.getViewRootImpl(); 98 setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null); 99 view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); 100 } 101 }; 102 103 private final Consumer<Boolean> mCrossWindowBlurListener = new Consumer<Boolean>() { 104 @Override 105 public void accept(Boolean enabled) { 106 mCrossWindowBlursEnabled = enabled; 107 dispatchTransactionSurface(mDepth); 108 } 109 }; 110 111 private final Launcher mLauncher; 112 /** 113 * Blur radius when completely zoomed out, in pixels. 114 */ 115 private int mMaxBlurRadius; 116 private boolean mCrossWindowBlursEnabled; 117 private WallpaperManagerCompat mWallpaperManager; 118 private SurfaceControl mSurface; 119 /** 120 * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. 121 * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) 122 */ 123 private float mDepth; 124 /** 125 * If we're launching and app and should not be blurring the screen for performance reasons. 126 */ 127 private boolean mBlurDisabledForAppLaunch; 128 129 // Workaround for animating the depth when multiwindow mode changes. 130 private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false; 131 132 private View.OnAttachStateChangeListener mOnAttachListener; 133 DepthController(Launcher l)134 public DepthController(Launcher l) { 135 mLauncher = l; 136 } 137 ensureDependencies()138 private void ensureDependencies() { 139 if (mWallpaperManager == null) { 140 mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); 141 mWallpaperManager = new WallpaperManagerCompat(mLauncher); 142 } 143 144 if (mLauncher.getRootView() != null && mOnAttachListener == null) { 145 mOnAttachListener = new View.OnAttachStateChangeListener() { 146 @Override 147 public void onViewAttachedToWindow(View view) { 148 // To handle the case where window token is invalid during last setDepth call. 149 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 150 if (windowToken != null) { 151 mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); 152 } 153 CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(), 154 mCrossWindowBlurListener); 155 } 156 157 @Override 158 public void onViewDetachedFromWindow(View view) { 159 CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener); 160 } 161 }; 162 mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); 163 if (mLauncher.getRootView().isAttachedToWindow()) { 164 CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(), 165 mCrossWindowBlurListener); 166 } 167 } 168 } 169 170 /** 171 * Sets if the underlying activity is started or not 172 */ setActivityStarted(boolean isStarted)173 public void setActivityStarted(boolean isStarted) { 174 if (isStarted) { 175 mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); 176 } else { 177 mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener); 178 setSurface(null); 179 } 180 } 181 182 /** 183 * Sets the specified app target surface to apply the blur to. 184 */ setSurface(SurfaceControl surface)185 public void setSurface(SurfaceControl surface) { 186 // Set launcher as the SurfaceControl when we don't need an external target anymore. 187 if (surface == null) { 188 ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); 189 surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null; 190 } 191 192 if (mSurface != surface) { 193 mSurface = surface; 194 if (surface != null) { 195 dispatchTransactionSurface(mDepth); 196 } 197 } 198 } 199 200 @Override setState(LauncherState toState)201 public void setState(LauncherState toState) { 202 if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) { 203 return; 204 } 205 206 float toDepth = toState.getDepth(mLauncher); 207 if (Float.compare(mDepth, toDepth) != 0) { 208 setDepth(toDepth); 209 } else if (toState == LauncherState.OVERVIEW) { 210 dispatchTransactionSurface(mDepth); 211 } 212 } 213 214 @Override setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation)215 public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, 216 PendingAnimation animation) { 217 if (config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER) 218 || mIgnoreStateChangesDuringMultiWindowAnimation) { 219 return; 220 } 221 222 float toDepth = toState.getDepth(mLauncher); 223 if (Float.compare(mDepth, toDepth) != 0) { 224 animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR)); 225 } 226 } 227 228 /** 229 * If we're launching an app from the home screen. 230 */ setIsInLaunchTransition(boolean inLaunchTransition)231 public void setIsInLaunchTransition(boolean inLaunchTransition) { 232 boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true); 233 mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled; 234 if (!inLaunchTransition) { 235 // Reset depth at the end of the launch animation, so the wallpaper won't be 236 // zoomed out if an app crashes. 237 setDepth(0f); 238 } 239 } 240 setDepth(float depth)241 private void setDepth(float depth) { 242 depth = Utilities.boundToRange(depth, 0, 1); 243 // Round out the depth to dedupe frequent, non-perceptable updates 244 int depthI = (int) (depth * 256); 245 float depthF = depthI / 256f; 246 if (Float.compare(mDepth, depthF) == 0) { 247 return; 248 } 249 if (dispatchTransactionSurface(depthF)) { 250 mDepth = depthF; 251 } 252 } 253 dispatchTransactionSurface(float depth)254 private boolean dispatchTransactionSurface(float depth) { 255 boolean supportsBlur = BlurUtils.supportsBlursOnWindows(); 256 if (supportsBlur && (mSurface == null || !mSurface.isValid())) { 257 return false; 258 } 259 ensureDependencies(); 260 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 261 if (windowToken != null) { 262 mWallpaperManager.setWallpaperZoomOut(windowToken, depth); 263 } 264 265 if (supportsBlur) { 266 // We cannot mark the window as opaque in overview because there will be an app window 267 // below the launcher layer, and we need to draw it -- without blurs. 268 boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW); 269 boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview; 270 271 int blur = opaque || isOverview || !mCrossWindowBlursEnabled 272 || mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius); 273 new SurfaceControl.Transaction() 274 .setBackgroundBlurRadius(mSurface, blur) 275 .setOpaque(mSurface, opaque) 276 .apply(); 277 } 278 return true; 279 } 280 281 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode)282 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { 283 mIgnoreStateChangesDuringMultiWindowAnimation = true; 284 285 ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH, 286 mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode)) 287 .setDuration(300); 288 mwAnimation.addListener(new AnimatorListenerAdapter() { 289 @Override 290 public void onAnimationEnd(Animator animation) { 291 mIgnoreStateChangesDuringMultiWindowAnimation = false; 292 } 293 }); 294 mwAnimation.setAutoCancel(true); 295 mwAnimation.start(); 296 } 297 } 298