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