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.AttachedSurfaceControl; 30 import android.view.CrossWindowBlurListeners; 31 import android.view.SurfaceControl; 32 import android.view.View; 33 import android.view.ViewRootImpl; 34 import android.view.ViewTreeObserver; 35 36 import com.android.launcher3.BaseActivity; 37 import com.android.launcher3.Launcher; 38 import com.android.launcher3.LauncherState; 39 import com.android.launcher3.R; 40 import com.android.launcher3.Utilities; 41 import com.android.launcher3.anim.PendingAnimation; 42 import com.android.launcher3.statemanager.StateManager.StateHandler; 43 import com.android.launcher3.states.StateAnimationConfig; 44 import com.android.systemui.shared.system.BlurUtils; 45 import com.android.systemui.shared.system.WallpaperManagerCompat; 46 47 import java.io.PrintWriter; 48 import java.util.function.Consumer; 49 50 /** 51 * Controls blur and wallpaper zoom, for the Launcher surface only. 52 */ 53 public class DepthController implements StateHandler<LauncherState>, 54 BaseActivity.MultiWindowModeChangedListener { 55 56 public static final FloatProperty<DepthController> DEPTH = 57 new FloatProperty<DepthController>("depth") { 58 @Override 59 public void setValue(DepthController depthController, float depth) { 60 depthController.setDepth(depth); 61 } 62 63 @Override 64 public Float get(DepthController depthController) { 65 return depthController.mDepth; 66 } 67 }; 68 69 /** 70 * A property that updates the background blur within a given range of values (ie. even if the 71 * animator goes beyond 0..1, the interpolated value will still be bounded). 72 */ 73 public static class ClampedDepthProperty extends FloatProperty<DepthController> { 74 private final float mMinValue; 75 private final float mMaxValue; 76 ClampedDepthProperty(float minValue, float maxValue)77 public ClampedDepthProperty(float minValue, float maxValue) { 78 super("depthClamped"); 79 mMinValue = minValue; 80 mMaxValue = maxValue; 81 } 82 83 @Override setValue(DepthController depthController, float depth)84 public void setValue(DepthController depthController, float depth) { 85 depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue)); 86 } 87 88 @Override get(DepthController depthController)89 public Float get(DepthController depthController) { 90 return depthController.mDepth; 91 } 92 } 93 94 private final ViewTreeObserver.OnDrawListener mOnDrawListener = 95 new ViewTreeObserver.OnDrawListener() { 96 @Override 97 public void onDraw() { 98 View view = mLauncher.getDragLayer(); 99 ViewRootImpl viewRootImpl = view.getViewRootImpl(); 100 boolean applied = setSurface( 101 viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null); 102 if (!applied) { 103 dispatchTransactionSurface(mDepth); 104 } 105 view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); 106 } 107 }; 108 109 private final Consumer<Boolean> mCrossWindowBlurListener = new Consumer<Boolean>() { 110 @Override 111 public void accept(Boolean enabled) { 112 mCrossWindowBlursEnabled = enabled; 113 dispatchTransactionSurface(mDepth); 114 } 115 }; 116 117 private final Runnable mOpaquenessListener = new Runnable() { 118 @Override 119 public void run() { 120 dispatchTransactionSurface(mDepth); 121 } 122 }; 123 124 private final Launcher mLauncher; 125 /** 126 * Blur radius when completely zoomed out, in pixels. 127 */ 128 private int mMaxBlurRadius; 129 private boolean mCrossWindowBlursEnabled; 130 private WallpaperManagerCompat mWallpaperManager; 131 private SurfaceControl mSurface; 132 /** 133 * How visible the -1 overlay is, from 0 to 1. 134 */ 135 private float mOverlayScrollProgress; 136 /** 137 * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. 138 * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) 139 */ 140 private float mDepth; 141 /** 142 * Last blur value, in pixels, that was applied. 143 * For debugging purposes. 144 */ 145 private int mCurrentBlur; 146 /** 147 * If we're launching and app and should not be blurring the screen for performance reasons. 148 */ 149 private boolean mBlurDisabledForAppLaunch; 150 /** 151 * If we requested early wake-up offsets to SurfaceFlinger. 152 */ 153 private boolean mInEarlyWakeUp; 154 155 // Workaround for animating the depth when multiwindow mode changes. 156 private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false; 157 158 // Hints that there is potentially content behind Launcher and that we shouldn't optimize by 159 // marking the launcher surface as opaque. Only used in certain Launcher states. 160 private boolean mHasContentBehindLauncher; 161 162 private View.OnAttachStateChangeListener mOnAttachListener; 163 DepthController(Launcher l)164 public DepthController(Launcher l) { 165 mLauncher = l; 166 } 167 ensureDependencies()168 private void ensureDependencies() { 169 if (mWallpaperManager == null) { 170 mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); 171 mWallpaperManager = new WallpaperManagerCompat(mLauncher); 172 } 173 174 if (mLauncher.getRootView() != null && mOnAttachListener == null) { 175 mOnAttachListener = new View.OnAttachStateChangeListener() { 176 @Override 177 public void onViewAttachedToWindow(View view) { 178 // To handle the case where window token is invalid during last setDepth call. 179 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 180 if (windowToken != null) { 181 mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); 182 } 183 onAttached(); 184 } 185 186 @Override 187 public void onViewDetachedFromWindow(View view) { 188 CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener); 189 mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener); 190 } 191 }; 192 mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); 193 if (mLauncher.getRootView().isAttachedToWindow()) { 194 onAttached(); 195 } 196 } 197 } 198 onAttached()199 private void onAttached() { 200 CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(), 201 mCrossWindowBlurListener); 202 mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener); 203 } 204 setHasContentBehindLauncher(boolean hasContentBehindLauncher)205 public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) { 206 mHasContentBehindLauncher = hasContentBehindLauncher; 207 } 208 209 /** 210 * Sets if the underlying activity is started or not 211 */ setActivityStarted(boolean isStarted)212 public void setActivityStarted(boolean isStarted) { 213 if (isStarted) { 214 mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); 215 } else { 216 mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener); 217 setSurface(null); 218 } 219 } 220 221 /** 222 * Sets the specified app target surface to apply the blur to. 223 * @return true when surface was valid and transaction was dispatched. 224 */ setSurface(SurfaceControl surface)225 public boolean setSurface(SurfaceControl surface) { 226 // Set launcher as the SurfaceControl when we don't need an external target anymore. 227 if (surface == null) { 228 ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); 229 surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null; 230 } 231 if (mSurface != surface) { 232 mSurface = surface; 233 if (surface != null) { 234 dispatchTransactionSurface(mDepth); 235 return true; 236 } 237 } 238 return false; 239 } 240 241 @Override setState(LauncherState toState)242 public void setState(LauncherState toState) { 243 if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) { 244 return; 245 } 246 247 float toDepth = toState.getDepth(mLauncher); 248 if (Float.compare(mDepth, toDepth) != 0) { 249 setDepth(toDepth); 250 } else if (toState == LauncherState.OVERVIEW) { 251 dispatchTransactionSurface(mDepth); 252 } else if (toState == LauncherState.BACKGROUND_APP) { 253 mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); 254 } 255 } 256 257 @Override setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation)258 public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, 259 PendingAnimation animation) { 260 if (config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER) 261 || mIgnoreStateChangesDuringMultiWindowAnimation) { 262 return; 263 } 264 265 float toDepth = toState.getDepth(mLauncher); 266 if (Float.compare(mDepth, toDepth) != 0) { 267 animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR)); 268 } 269 } 270 271 /** 272 * If we're launching an app from the home screen. 273 */ setIsInLaunchTransition(boolean inLaunchTransition)274 public void setIsInLaunchTransition(boolean inLaunchTransition) { 275 boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true); 276 mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled; 277 if (!inLaunchTransition) { 278 // Reset depth at the end of the launch animation, so the wallpaper won't be 279 // zoomed out if an app crashes. 280 setDepth(0f); 281 } 282 } 283 setDepth(float depth)284 private void setDepth(float depth) { 285 depth = Utilities.boundToRange(depth, 0, 1); 286 // Round out the depth to dedupe frequent, non-perceptable updates 287 int depthI = (int) (depth * 256); 288 float depthF = depthI / 256f; 289 if (Float.compare(mDepth, depthF) == 0) { 290 return; 291 } 292 dispatchTransactionSurface(depthF); 293 mDepth = depthF; 294 } 295 onOverlayScrollChanged(float progress)296 public void onOverlayScrollChanged(float progress) { 297 // Round out the progress to dedupe frequent, non-perceptable updates 298 int progressI = (int) (progress * 256); 299 float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f); 300 if (Float.compare(mOverlayScrollProgress, progressF) == 0) { 301 return; 302 } 303 mOverlayScrollProgress = progressF; 304 dispatchTransactionSurface(mDepth); 305 } 306 dispatchTransactionSurface(float depth)307 private boolean dispatchTransactionSurface(float depth) { 308 boolean supportsBlur = BlurUtils.supportsBlursOnWindows(); 309 if (supportsBlur && (mSurface == null || !mSurface.isValid())) { 310 return false; 311 } 312 ensureDependencies(); 313 depth = Math.max(depth, mOverlayScrollProgress); 314 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 315 if (windowToken != null) { 316 mWallpaperManager.setWallpaperZoomOut(windowToken, depth); 317 } 318 319 if (supportsBlur) { 320 boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque(); 321 boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg; 322 323 mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg 324 ? 0 : (int) (depth * mMaxBlurRadius); 325 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction() 326 .setBackgroundBlurRadius(mSurface, mCurrentBlur) 327 .setOpaque(mSurface, isSurfaceOpaque); 328 329 // Set early wake-up flags when we know we're executing an expensive operation, this way 330 // SurfaceFlinger will adjust its internal offsets to avoid jank. 331 boolean wantsEarlyWakeUp = depth > 0 && depth < 1; 332 if (wantsEarlyWakeUp && !mInEarlyWakeUp) { 333 transaction.setEarlyWakeupStart(); 334 mInEarlyWakeUp = true; 335 } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) { 336 transaction.setEarlyWakeupEnd(); 337 mInEarlyWakeUp = false; 338 } 339 340 AttachedSurfaceControl rootSurfaceControl = 341 mLauncher.getRootView().getRootSurfaceControl(); 342 if (rootSurfaceControl != null) { 343 rootSurfaceControl.applyTransactionOnDraw(transaction); 344 } 345 } 346 return true; 347 } 348 349 @Override 350 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { 351 mIgnoreStateChangesDuringMultiWindowAnimation = true; 352 353 ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH, 354 mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode)) 355 .setDuration(300); 356 mwAnimation.addListener(new AnimatorListenerAdapter() { 357 @Override 358 public void onAnimationEnd(Animator animation) { 359 mIgnoreStateChangesDuringMultiWindowAnimation = false; 360 } 361 }); 362 mwAnimation.setAutoCancel(true); 363 mwAnimation.start(); 364 } 365 366 public void dump(String prefix, PrintWriter writer) { 367 writer.println(prefix + this.getClass().getSimpleName()); 368 writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius); 369 writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled); 370 writer.println(prefix + "\tmSurface=" + mSurface); 371 writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress); 372 writer.println(prefix + "\tmDepth=" + mDepth); 373 writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur); 374 writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch); 375 writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp); 376 writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation=" 377 + mIgnoreStateChangesDuringMultiWindowAnimation); 378 } 379 } 380