• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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