• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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