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.util.FloatProperty; 28 import android.view.View; 29 import android.view.ViewTreeObserver; 30 31 import com.android.launcher3.BaseActivity; 32 import com.android.launcher3.Launcher; 33 import com.android.launcher3.LauncherState; 34 import com.android.launcher3.R; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.anim.PendingAnimation; 37 import com.android.launcher3.statemanager.StateManager.StateHandler; 38 import com.android.launcher3.states.StateAnimationConfig; 39 import com.android.systemui.shared.system.BlurUtils; 40 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 41 import com.android.systemui.shared.system.SurfaceControlCompat; 42 import com.android.systemui.shared.system.TransactionCompat; 43 import com.android.systemui.shared.system.WallpaperManagerCompat; 44 45 /** 46 * Controls blur and wallpaper zoom, for the Launcher surface only. 47 */ 48 public class DepthController implements StateHandler<LauncherState>, 49 BaseActivity.MultiWindowModeChangedListener { 50 51 public static final FloatProperty<DepthController> DEPTH = 52 new FloatProperty<DepthController>("depth") { 53 @Override 54 public void setValue(DepthController depthController, float depth) { 55 depthController.setDepth(depth); 56 } 57 58 @Override 59 public Float get(DepthController depthController) { 60 return depthController.mDepth; 61 } 62 }; 63 64 /** 65 * A property that updates the background blur within a given range of values (ie. even if the 66 * animator goes beyond 0..1, the interpolated value will still be bounded). 67 */ 68 public static class ClampedDepthProperty extends FloatProperty<DepthController> { 69 private final float mMinValue; 70 private final float mMaxValue; 71 ClampedDepthProperty(float minValue, float maxValue)72 public ClampedDepthProperty(float minValue, float maxValue) { 73 super("depthClamped"); 74 mMinValue = minValue; 75 mMaxValue = maxValue; 76 } 77 78 @Override setValue(DepthController depthController, float depth)79 public void setValue(DepthController depthController, float depth) { 80 depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue)); 81 } 82 83 @Override get(DepthController depthController)84 public Float get(DepthController depthController) { 85 return depthController.mDepth; 86 } 87 } 88 89 private final ViewTreeObserver.OnDrawListener mOnDrawListener = 90 new ViewTreeObserver.OnDrawListener() { 91 @Override 92 public void onDraw() { 93 View view = mLauncher.getDragLayer(); 94 setSurface(new SurfaceControlCompat(view)); 95 view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); 96 } 97 }; 98 99 private final Launcher mLauncher; 100 /** 101 * Blur radius when completely zoomed out, in pixels. 102 */ 103 private int mMaxBlurRadius; 104 private WallpaperManagerCompat mWallpaperManager; 105 private SurfaceControlCompat mSurface; 106 /** 107 * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. 108 * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) 109 */ 110 private float mDepth; 111 112 // Workaround for animating the depth when multiwindow mode changes. 113 private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false; 114 115 private View.OnAttachStateChangeListener mOnAttachListener; 116 DepthController(Launcher l)117 public DepthController(Launcher l) { 118 mLauncher = l; 119 } 120 ensureDependencies()121 private void ensureDependencies() { 122 if (mWallpaperManager == null) { 123 mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); 124 mWallpaperManager = new WallpaperManagerCompat(mLauncher); 125 } 126 if (mLauncher.getRootView() != null && mOnAttachListener == null) { 127 mOnAttachListener = new View.OnAttachStateChangeListener() { 128 @Override 129 public void onViewAttachedToWindow(View view) { 130 // To handle the case where window token is invalid during last setDepth call. 131 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 132 if (windowToken != null) { 133 mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); 134 } 135 } 136 137 @Override 138 public void onViewDetachedFromWindow(View view) { 139 } 140 }; 141 mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); 142 } 143 } 144 145 /** 146 * Sets if the underlying activity is started or not 147 */ setActivityStarted(boolean isStarted)148 public void setActivityStarted(boolean isStarted) { 149 if (isStarted) { 150 mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); 151 } else { 152 mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener); 153 setSurface(null); 154 } 155 } 156 157 /** 158 * Sets the specified app target surface to apply the blur to. 159 */ setSurfaceToApp(RemoteAnimationTargetCompat target)160 public void setSurfaceToApp(RemoteAnimationTargetCompat target) { 161 if (target != null) { 162 setSurface(target.leash); 163 } else { 164 setActivityStarted(mLauncher.isStarted()); 165 } 166 } 167 setSurface(SurfaceControlCompat surface)168 private void setSurface(SurfaceControlCompat surface) { 169 if (mSurface != surface) { 170 mSurface = surface; 171 if (surface != null) { 172 setDepth(mDepth); 173 } else { 174 // If there is no surface, then reset the ratio 175 setDepth(0f); 176 } 177 } 178 } 179 180 @Override setState(LauncherState toState)181 public void setState(LauncherState toState) { 182 if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) { 183 return; 184 } 185 186 float toDepth = toState.getDepth(mLauncher); 187 if (Float.compare(mDepth, toDepth) != 0) { 188 setDepth(toDepth); 189 } 190 } 191 192 @Override setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation)193 public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, 194 PendingAnimation animation) { 195 if (mSurface == null 196 || config.onlyPlayAtomicComponent() 197 || config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER) 198 || mIgnoreStateChangesDuringMultiWindowAnimation) { 199 return; 200 } 201 202 float toDepth = toState.getDepth(mLauncher); 203 if (Float.compare(mDepth, toDepth) != 0) { 204 animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR)); 205 } 206 } 207 setDepth(float depth)208 private void setDepth(float depth) { 209 depth = Utilities.boundToRange(depth, 0, 1); 210 // Round out the depth to dedupe frequent, non-perceptable updates 211 int depthI = (int) (depth * 256); 212 float depthF = depthI / 256f; 213 if (Float.compare(mDepth, depthF) == 0) { 214 return; 215 } 216 217 boolean supportsBlur = BlurUtils.supportsBlursOnWindows(); 218 if (supportsBlur && (mSurface == null || !mSurface.isValid())) { 219 return; 220 } 221 mDepth = depthF; 222 ensureDependencies(); 223 IBinder windowToken = mLauncher.getRootView().getWindowToken(); 224 if (windowToken != null) { 225 mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); 226 } 227 228 if (supportsBlur) { 229 final int blur; 230 if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { 231 // All apps has a solid background. We don't need to draw blurs after it's fully 232 // visible. This will take us out of GPU composition, saving battery and increasing 233 // performance. 234 blur = 0; 235 } else { 236 blur = (int) (mDepth * mMaxBlurRadius); 237 } 238 new TransactionCompat() 239 .setBackgroundBlurRadius(mSurface, blur) 240 .apply(); 241 } 242 } 243 244 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode)245 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { 246 mIgnoreStateChangesDuringMultiWindowAnimation = true; 247 248 ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH, 249 mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode)) 250 .setDuration(300); 251 mwAnimation.addListener(new AnimatorListenerAdapter() { 252 @Override 253 public void onAnimationEnd(Animator animation) { 254 mIgnoreStateChangesDuringMultiWindowAnimation = false; 255 } 256 }); 257 mwAnimation.setAutoCancel(true); 258 mwAnimation.start(); 259 } 260 } 261