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.launcher3.taskbar.overlay; 17 18 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; 20 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 21 22 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; 23 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; 24 import static com.android.launcher3.LauncherState.ALL_APPS; 25 26 import android.annotation.SuppressLint; 27 import android.content.Context; 28 import android.graphics.PixelFormat; 29 import android.util.Log; 30 import android.view.AttachedSurfaceControl; 31 import android.view.Gravity; 32 import android.view.MotionEvent; 33 import android.view.SurfaceControl; 34 import android.view.ViewRootImpl; 35 import android.view.WindowManager; 36 import android.view.WindowManager.LayoutParams; 37 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.AbstractFloatingView; 41 import com.android.launcher3.DeviceProfile; 42 import com.android.launcher3.Flags; 43 import com.android.launcher3.R; 44 import com.android.launcher3.taskbar.TaskbarActivityContext; 45 import com.android.launcher3.taskbar.TaskbarControllers; 46 import com.android.systemui.shared.system.BlurUtils; 47 import com.android.systemui.shared.system.TaskStackChangeListener; 48 import com.android.systemui.shared.system.TaskStackChangeListeners; 49 50 import java.util.Optional; 51 52 /** 53 * Handles the Taskbar overlay window lifecycle. 54 * <p> 55 * Overlays need to be inflated in a separate window so that have the correct hierarchy. For 56 * instance, they need to be below the notification tray. If there are multiple overlays open, the 57 * same window is used. 58 */ 59 public final class TaskbarOverlayController { 60 61 private static final String TAG = "TaskbarOverlayController"; 62 private static final String WINDOW_TITLE = "Taskbar Overlay"; 63 64 private final TaskbarActivityContext mTaskbarContext; 65 private final Context mWindowContext; 66 private final TaskbarOverlayProxyView mProxyView; 67 private final LayoutParams mLayoutParams; 68 private final int mMaxBlurRadius; 69 70 private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 71 @Override 72 public void onTaskMovedToFront(int taskId) { 73 // New front task will be below existing overlay, so move out of the way. 74 hideWindowOnTaskStackChange(); 75 } 76 77 @Override 78 public void onTaskStackChanged() { 79 // The other callbacks are insufficient for All Apps, because there are many cases where 80 // it can relaunch the same task already behind it. However, this callback needs to be a 81 // no-op when only EDU is shown, because going between the EDU steps invokes this 82 // callback. 83 if (mControllers.getSharedState() != null 84 && mControllers.getSharedState().allAppsVisible) { 85 hideWindowOnTaskStackChange(); 86 } 87 } 88 89 private void hideWindowOnTaskStackChange() { 90 // A task was launched while overlay window was open, so stash Taskbar. 91 mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); 92 hideWindow(); 93 } 94 }; 95 96 private DeviceProfile mLauncherDeviceProfile; 97 private @Nullable TaskbarOverlayContext mOverlayContext; 98 private TaskbarControllers mControllers; // Initialized in init. 99 // True if we have alerted surface flinger of an expensive call for blur. 100 private boolean mInEarlyWakeUp; 101 TaskbarOverlayController( TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile)102 public TaskbarOverlayController( 103 TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile) { 104 mTaskbarContext = taskbarContext; 105 mWindowContext = mTaskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null); 106 mProxyView = new TaskbarOverlayProxyView(); 107 mLayoutParams = createLayoutParams(); 108 mLauncherDeviceProfile = launcherDeviceProfile; 109 mMaxBlurRadius = mTaskbarContext.getResources().getDimensionPixelSize( 110 R.dimen.max_depth_blur_radius_enhanced); 111 } 112 113 /** Initialize the controller. */ init(TaskbarControllers controllers)114 public void init(TaskbarControllers controllers) { 115 mControllers = controllers; 116 } 117 118 /** 119 * Creates a window for Taskbar overlays, if it does not already exist. Returns the window 120 * context for the current overlay window. 121 */ requestWindow()122 public TaskbarOverlayContext requestWindow() { 123 if (mOverlayContext == null) { 124 mOverlayContext = new TaskbarOverlayContext( 125 mWindowContext, mTaskbarContext, mControllers); 126 } 127 128 if (!mProxyView.isOpen()) { 129 mProxyView.show(); 130 Optional.ofNullable(mOverlayContext.getSystemService(WindowManager.class)) 131 .ifPresent(m -> m.addView(mOverlayContext.getDragLayer(), mLayoutParams)); 132 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 133 } 134 135 return mOverlayContext; 136 } 137 138 /** Hides the current overlay window with animation. */ hideWindow()139 public void hideWindow() { 140 mProxyView.close(true); 141 } 142 143 /** 144 * Removes the overlay window from the hierarchy, if all floating views are closed and there is 145 * no system drag operation in progress. 146 * <p> 147 * This method should be called after an exit animation finishes, if applicable. 148 */ maybeCloseWindow()149 void maybeCloseWindow() { 150 if (!canCloseWindow()) return; 151 mProxyView.close(false); 152 onDestroy(); 153 } 154 155 @SuppressLint("WrongConstant") canCloseWindow()156 private boolean canCloseWindow() { 157 if (mOverlayContext == null) return true; 158 if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false; 159 return !mOverlayContext.getDragController().isSystemDragInProgress(); 160 } 161 162 /** Destroys the controller and any overlay window if present. */ onDestroy()163 public void onDestroy() { 164 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 165 Optional.ofNullable(mOverlayContext).ifPresent(c -> { 166 c.onDestroy(); 167 WindowManager wm = c.getSystemService(WindowManager.class); 168 if (wm != null) { 169 wm.removeViewImmediate(mOverlayContext.getDragLayer()); 170 } 171 }); 172 mOverlayContext = null; 173 } 174 175 /** The current device profile for the overlay window. */ getLauncherDeviceProfile()176 public DeviceProfile getLauncherDeviceProfile() { 177 return mLauncherDeviceProfile; 178 } 179 180 /** Updates {@link DeviceProfile} instance for Taskbar's overlay window. */ updateLauncherDeviceProfile(DeviceProfile dp)181 public void updateLauncherDeviceProfile(DeviceProfile dp) { 182 mLauncherDeviceProfile = dp; 183 Optional.ofNullable(mOverlayContext).ifPresent(c -> { 184 AbstractFloatingView.closeAllOpenViewsExcept(c, false, TYPE_REBIND_SAFE); 185 c.dispatchDeviceProfileChanged(); 186 }); 187 } 188 189 /** The default open duration for overlays. */ getOpenDuration()190 public int getOpenDuration() { 191 return ALL_APPS.getTransitionDuration(mTaskbarContext, true); 192 } 193 194 /** The default close duration for overlays. */ getCloseDuration()195 public int getCloseDuration() { 196 return ALL_APPS.getTransitionDuration(mTaskbarContext, false); 197 } 198 199 @SuppressLint("WrongConstant") createLayoutParams()200 private LayoutParams createLayoutParams() { 201 LayoutParams layoutParams = new LayoutParams( 202 TYPE_APPLICATION_OVERLAY, 203 /* flags = */ 0, 204 PixelFormat.TRANSLUCENT); 205 layoutParams.setTitle(WINDOW_TITLE); 206 layoutParams.gravity = Gravity.BOTTOM; 207 layoutParams.packageName = mTaskbarContext.getPackageName(); 208 layoutParams.setFitInsetsTypes(0); // Handled by container view. 209 layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 210 layoutParams.setSystemApplicationOverlay(true); 211 layoutParams.privateFlags = PRIVATE_FLAG_CONSUME_IME_INSETS; 212 return layoutParams; 213 } 214 215 /** 216 * Sets the blur radius for the overlay window. 217 * 218 * @param radius the blur radius in pixels. This will automatically change to {@code 0} if blurs 219 * are unsupported on the device. 220 */ setBackgroundBlurRadius(int radius)221 public void setBackgroundBlurRadius(int radius) { 222 if (!Flags.allAppsBlur()) { 223 return; 224 } 225 if (!BlurUtils.supportsBlursOnWindows()) { 226 Log.d(TAG, "setBackgroundBlurRadius: not supported, setting to 0"); 227 radius = 0; 228 // intentionally falling through in case a non-0 blur was previously set. 229 } 230 if (mOverlayContext == null) { 231 Log.w(TAG, "setBackgroundBlurRadius: no overlay context"); 232 return; 233 } 234 TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer(); 235 if (dragLayer == null) { 236 Log.w(TAG, "setBackgroundBlurRadius: no drag layer"); 237 return; 238 } 239 ViewRootImpl dragLayerViewRoot = dragLayer.getViewRootImpl(); 240 if (dragLayerViewRoot == null) { 241 Log.w(TAG, "setBackgroundBlurRadius: dragLayerViewRoot is null"); 242 return; 243 } 244 AttachedSurfaceControl rootSurfaceControl = dragLayer.getRootSurfaceControl(); 245 if (rootSurfaceControl == null) { 246 Log.w(TAG, "setBackgroundBlurRadius: rootSurfaceControl is null"); 247 return; 248 } 249 SurfaceControl surfaceControl = dragLayerViewRoot.getSurfaceControl(); 250 if (surfaceControl == null || !surfaceControl.isValid()) { 251 Log.w(TAG, "setBackgroundBlurRadius: surfaceControl is null or invalid"); 252 return; 253 } 254 Log.v(TAG, "setBackgroundBlurRadius: " + radius); 255 SurfaceControl.Transaction transaction = 256 new SurfaceControl.Transaction().setBackgroundBlurRadius(surfaceControl, radius); 257 258 // Set early wake-up flags when we know we're executing an expensive operation, this way 259 // SurfaceFlinger will adjust its internal offsets to avoid jank. 260 boolean wantsEarlyWakeUp = radius > 0 && radius < mMaxBlurRadius; 261 if (wantsEarlyWakeUp && !mInEarlyWakeUp) { 262 Log.d(TAG, "setBackgroundBlurRadius: setting early wakeup"); 263 transaction.setEarlyWakeupStart(); 264 mInEarlyWakeUp = true; 265 } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) { 266 Log.d(TAG, "setBackgroundBlurRadius: clearing early wakeup"); 267 transaction.setEarlyWakeupEnd(); 268 mInEarlyWakeUp = false; 269 } 270 271 rootSurfaceControl.applyTransactionOnDraw(transaction); 272 } 273 274 /** 275 * Proxy view connecting taskbar drag layer to the overlay window. 276 * 277 * Overlays are in a separate window and has its own drag layer, but this proxy lets its views 278 * behave as though they are in the taskbar drag layer. For instance, when the taskbar closes 279 * all {@link AbstractFloatingView} instances, the overlay window will also close. 280 */ 281 private class TaskbarOverlayProxyView extends AbstractFloatingView { 282 283 private TaskbarOverlayProxyView() { 284 super(mTaskbarContext, null); 285 } 286 287 private void show() { 288 mIsOpen = true; 289 mTaskbarContext.getDragLayer().addView(this); 290 } 291 292 @Override 293 protected void handleClose(boolean animate) { 294 if (!mIsOpen) return; 295 if (Flags.taskbarOverflow()) { 296 // Mark the view closed before attempting to remove it, so the drag layer does not 297 // schedule another call to close. Needed for taskbar overflow in case the KQS 298 // view shown for taskbar overflow needs to be reshown - delayed close call would 299 // would result in reshown KQS view getting hidden. 300 mIsOpen = false; 301 } 302 mTaskbarContext.getDragLayer().removeView(this); 303 Optional.ofNullable(mOverlayContext).ifPresent(c -> { 304 if (canCloseWindow()) { 305 onDestroy(); // Window is already ready to be destroyed. 306 } else { 307 // Close window's AFVs before destroying it. Its drag layer will attempt to 308 // close the proxy view again once its children are removed. 309 closeAllOpenViews(c, animate); 310 } 311 }); 312 } 313 314 @Override isOfType(int type)315 protected boolean isOfType(int type) { 316 return (type & TYPE_TASKBAR_OVERLAY_PROXY) != 0; 317 } 318 319 @Override onControllerInterceptTouchEvent(MotionEvent ev)320 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 321 return false; 322 } 323 } 324 } 325