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