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