1 /*
2  * Copyright (C) 2022 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 package com.android.quickstep.util;
17 
18 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
19 
20 import android.app.WallpaperManager;
21 import android.os.IBinder;
22 import android.util.FloatProperty;
23 import android.util.Log;
24 import android.view.AttachedSurfaceControl;
25 import android.view.SurfaceControl;
26 
27 import com.android.launcher3.Launcher;
28 import com.android.launcher3.R;
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.util.MultiPropertyFactory;
31 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
32 import com.android.systemui.shared.system.BlurUtils;
33 
34 /**
35  * Utility class for applying depth effect
36  */
37 public class BaseDepthController {
38     public static final float DEPTH_0_PERCENT = 0f;
39     public static final float DEPTH_60_PERCENT = 0.6f;
40     public static final float DEPTH_70_PERCENT = 0.7f;
41 
42     private static final FloatProperty<BaseDepthController> DEPTH =
43             new FloatProperty<BaseDepthController>("depth") {
44                 @Override
45                 public void setValue(BaseDepthController depthController, float depth) {
46                     depthController.setDepth(depth);
47                 }
48 
49                 @Override
50                 public Float get(BaseDepthController depthController) {
51                     return depthController.mDepth;
52                 }
53             };
54 
55     private static final int DEPTH_INDEX_STATE_TRANSITION = 0;
56     private static final int DEPTH_INDEX_WIDGET = 1;
57     private static final int DEPTH_INDEX_COUNT = 2;
58 
59     // b/291401432
60     private static final String TAG = "BaseDepthController";
61 
62     protected final Launcher mLauncher;
63     /** Property to set the depth for state transition. */
64     public final MultiProperty stateDepth;
65     /** Property to set the depth for widget picker. */
66     public final MultiProperty widgetDepth;
67 
68     /**
69      * Blur radius when completely zoomed out, in pixels.
70      */
71     protected final int mMaxBlurRadius;
72     protected final WallpaperManager mWallpaperManager;
73     protected boolean mCrossWindowBlursEnabled;
74 
75     /**
76      * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
77      * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
78      */
79     private float mDepth;
80 
81     protected SurfaceControl mSurface;
82 
83     // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
84     // marking the launcher surface as opaque.  Only used in certain Launcher states.
85     private boolean mHasContentBehindLauncher;
86 
87     /** Pause blur but allow transparent, can be used when launch something behind the Launcher. */
88     protected boolean mPauseBlurs;
89 
90     /**
91      * Last blur value, in pixels, that was applied.
92      * For debugging purposes.
93      */
94     protected int mCurrentBlur;
95     /**
96      * If we requested early wake-up offsets to SurfaceFlinger.
97      */
98     protected boolean mInEarlyWakeUp;
99 
100     protected boolean mWaitingOnSurfaceValidity;
101 
BaseDepthController(Launcher activity)102     public BaseDepthController(Launcher activity) {
103         mLauncher = activity;
104         mMaxBlurRadius = activity.getResources().getInteger(R.integer.max_depth_blur_radius);
105         mWallpaperManager = activity.getSystemService(WallpaperManager.class);
106 
107         MultiPropertyFactory<BaseDepthController> depthProperty =
108                 new MultiPropertyFactory<>(this, DEPTH, DEPTH_INDEX_COUNT, Float::max);
109         stateDepth = depthProperty.get(DEPTH_INDEX_STATE_TRANSITION);
110         widgetDepth = depthProperty.get(DEPTH_INDEX_WIDGET);
111     }
112 
setCrossWindowBlursEnabled(boolean isEnabled)113     protected void setCrossWindowBlursEnabled(boolean isEnabled) {
114         mCrossWindowBlursEnabled = isEnabled;
115         applyDepthAndBlur();
116     }
117 
setHasContentBehindLauncher(boolean hasContentBehindLauncher)118     public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
119         mHasContentBehindLauncher = hasContentBehindLauncher;
120     }
121 
pauseBlursOnWindows(boolean pause)122     public void pauseBlursOnWindows(boolean pause) {
123         if (pause != mPauseBlurs) {
124             mPauseBlurs = pause;
125             applyDepthAndBlur();
126         }
127     }
128 
onInvalidSurface()129     protected void onInvalidSurface() { }
130 
applyDepthAndBlur()131     protected void applyDepthAndBlur() {
132         float depth = mDepth;
133         IBinder windowToken = mLauncher.getRootView().getWindowToken();
134         if (windowToken != null) {
135             if (enableScalingRevealHomeAnimation()) {
136                 mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
137             } else {
138                 // The API's full zoom-out is three times larger than the zoom-out we apply to the
139                 // icons. To keep the two consistent throughout the animation while keeping
140                 // Launcher's concept of full depth unchanged, we divide the depth by 3 here.
141                 mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
142             }
143         }
144 
145         if (!BlurUtils.supportsBlursOnWindows()) {
146             return;
147         }
148         if (mSurface == null) {
149             Log.d(TAG, "mSurface is null and mCurrentBlur is: " + mCurrentBlur);
150             return;
151         }
152         if (!mSurface.isValid()) {
153             Log.d(TAG, "mSurface is not valid");
154             mWaitingOnSurfaceValidity = true;
155             onInvalidSurface();
156             return;
157         }
158         mWaitingOnSurfaceValidity = false;
159         boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
160         boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs;
161 
162         float blurAmount;
163         if (enableScalingRevealHomeAnimation()) {
164             blurAmount = mapDepthToBlur(depth);
165         } else {
166             blurAmount = depth;
167         }
168         mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
169                 ? 0 : (int) (blurAmount * mMaxBlurRadius);
170 
171         SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
172                 .setBackgroundBlurRadius(mSurface, mCurrentBlur)
173                 .setOpaque(mSurface, isSurfaceOpaque);
174 
175         // Set early wake-up flags when we know we're executing an expensive operation, this way
176         // SurfaceFlinger will adjust its internal offsets to avoid jank.
177         boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
178         if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
179             transaction.setEarlyWakeupStart();
180             mInEarlyWakeUp = true;
181         } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
182             transaction.setEarlyWakeupEnd();
183             mInEarlyWakeUp = false;
184         }
185 
186         AttachedSurfaceControl rootSurfaceControl =
187                 mLauncher.getRootView().getRootSurfaceControl();
188         if (rootSurfaceControl != null) {
189             rootSurfaceControl.applyTransactionOnDraw(transaction);
190         }
191     }
192 
193     private void setDepth(float depth) {
194         depth = Utilities.boundToRange(depth, 0, 1);
195         // Round out the depth to dedupe frequent, non-perceptable updates
196         int depthI = (int) (depth * 256);
197         float depthF = depthI / 256f;
198         if (Float.compare(mDepth, depthF) == 0) {
199             return;
200         }
201         mDepth = depthF;
202         applyDepthAndBlur();
203     }
204 
205     /**
206      * Sets the specified app target surface to apply the blur to.
207      */
208     protected void setSurface(SurfaceControl surface) {
209         if (mSurface != surface || mWaitingOnSurfaceValidity) {
210             mSurface = surface;
211             Log.d(TAG, "setSurface:\n\tmWaitingOnSurfaceValidity: " + mWaitingOnSurfaceValidity
212                     + "\n\tmSurface: " + mSurface);
213             applyDepthAndBlur();
214         }
215     }
216 
217     /**
218      * Maps depth values to blur amounts as a percentage of the max blur.
219      * The blur percentage grows linearly with depth, and maxes out at 30% depth.
220      */
221     private static float mapDepthToBlur(float depth) {
222         return Math.min(3 * depth, 1f);
223     }
224 }
225