• 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.TYPE_APPLICATION_OVERLAY;
20 
21 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
22 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
23 import static com.android.launcher3.LauncherState.ALL_APPS;
24 
25 import android.annotation.SuppressLint;
26 import android.content.Context;
27 import android.graphics.PixelFormat;
28 import android.view.Gravity;
29 import android.view.MotionEvent;
30 import android.view.WindowManager;
31 import android.view.WindowManager.LayoutParams;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.AbstractFloatingView;
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.taskbar.TaskbarActivityContext;
38 import com.android.launcher3.taskbar.TaskbarControllers;
39 import com.android.systemui.shared.system.TaskStackChangeListener;
40 import com.android.systemui.shared.system.TaskStackChangeListeners;
41 
42 import java.util.Optional;
43 
44 /**
45  * Handles the Taskbar overlay window lifecycle.
46  * <p>
47  * Overlays need to be inflated in a separate window so that have the correct hierarchy. For
48  * instance, they need to be below the notification tray. If there are multiple overlays open, the
49  * same window is used.
50  */
51 public final class TaskbarOverlayController {
52 
53     private static final String WINDOW_TITLE = "Taskbar Overlay";
54 
55     private final TaskbarActivityContext mTaskbarContext;
56     private final Context mWindowContext;
57     private final TaskbarOverlayProxyView mProxyView;
58     private final LayoutParams mLayoutParams;
59 
60     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
61         @Override
62         public void onTaskStackChanged() {
63             mProxyView.close(false);
64         }
65 
66         @Override
67         public void onTaskMovedToFront(int taskId) {
68             mProxyView.close(false);
69         }
70     };
71 
72     private DeviceProfile mLauncherDeviceProfile;
73     private @Nullable TaskbarOverlayContext mOverlayContext;
74     private TaskbarControllers mControllers; // Initialized in init.
75 
TaskbarOverlayController( TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile)76     public TaskbarOverlayController(
77             TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile) {
78         mTaskbarContext = taskbarContext;
79         mWindowContext = mTaskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
80         mProxyView = new TaskbarOverlayProxyView();
81         mLayoutParams = createLayoutParams();
82         mLauncherDeviceProfile = launcherDeviceProfile;
83     }
84 
85     /** Initialize the controller. */
init(TaskbarControllers controllers)86     public void init(TaskbarControllers controllers) {
87         mControllers = controllers;
88     }
89 
90     /**
91      * Creates a window for Taskbar overlays, if it does not already exist. Returns the window
92      * context for the current overlay window.
93      */
requestWindow()94     public TaskbarOverlayContext requestWindow() {
95         if (mOverlayContext == null) {
96             mOverlayContext = new TaskbarOverlayContext(
97                     mWindowContext, mTaskbarContext, mControllers);
98         }
99 
100         if (!mProxyView.isOpen()) {
101             mProxyView.show();
102             Optional.ofNullable(mOverlayContext.getSystemService(WindowManager.class))
103                     .ifPresent(m -> m.addView(mOverlayContext.getDragLayer(), mLayoutParams));
104             TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
105         }
106 
107         return mOverlayContext;
108     }
109 
110     /** Hides the current overlay window with animation. */
hideWindow()111     public void hideWindow() {
112         mProxyView.close(true);
113     }
114 
115     /**
116      * Removes the overlay window from the hierarchy, if all floating views are closed and there is
117      * no system drag operation in progress.
118      * <p>
119      * This method should be called after an exit animation finishes, if applicable.
120      */
121     @SuppressLint("WrongConstant")
maybeCloseWindow()122     void maybeCloseWindow() {
123         if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)
124                 || mOverlayContext.getDragController().isSystemDragInProgress())) {
125             return;
126         }
127         mProxyView.close(false);
128         onDestroy();
129     }
130 
131     /** Destroys the controller and any overlay window if present. */
onDestroy()132     public void onDestroy() {
133         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
134         Optional.ofNullable(mOverlayContext)
135                 .map(c -> c.getSystemService(WindowManager.class))
136                 .ifPresent(m -> m.removeViewImmediate(mOverlayContext.getDragLayer()));
137         mOverlayContext = null;
138     }
139 
140     /** The current device profile for the overlay window. */
getLauncherDeviceProfile()141     public DeviceProfile getLauncherDeviceProfile() {
142         return mLauncherDeviceProfile;
143     }
144 
145     /** Updates {@link DeviceProfile} instance for Taskbar's overlay window. */
updateLauncherDeviceProfile(DeviceProfile dp)146     public void updateLauncherDeviceProfile(DeviceProfile dp) {
147         mLauncherDeviceProfile = dp;
148         Optional.ofNullable(mOverlayContext).ifPresent(c -> {
149             AbstractFloatingView.closeAllOpenViewsExcept(c, false, TYPE_REBIND_SAFE);
150             c.dispatchDeviceProfileChanged();
151         });
152     }
153 
154     /** The default open duration for overlays. */
getOpenDuration()155     public int getOpenDuration() {
156         return ALL_APPS.getTransitionDuration(mTaskbarContext, true);
157     }
158 
159     /** The default close duration for overlays. */
getCloseDuration()160     public int getCloseDuration() {
161         return ALL_APPS.getTransitionDuration(mTaskbarContext, false);
162     }
163 
164     @SuppressLint("WrongConstant")
createLayoutParams()165     private LayoutParams createLayoutParams() {
166         LayoutParams layoutParams = new LayoutParams(
167                 TYPE_APPLICATION_OVERLAY,
168                 LayoutParams.FLAG_SPLIT_TOUCH,
169                 PixelFormat.TRANSLUCENT);
170         layoutParams.setTitle(WINDOW_TITLE);
171         layoutParams.gravity = Gravity.BOTTOM;
172         layoutParams.packageName = mTaskbarContext.getPackageName();
173         layoutParams.setFitInsetsTypes(0); // Handled by container view.
174         layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
175         layoutParams.setSystemApplicationOverlay(true);
176         return layoutParams;
177     }
178 
179     /**
180      * Proxy view connecting taskbar drag layer to the overlay window.
181      *
182      * Overlays are in a separate window and has its own drag layer, but this proxy lets its views
183      * behave as though they are in the taskbar drag layer. For instance, when the taskbar closes
184      * all {@link AbstractFloatingView} instances, the overlay window will also close.
185      */
186     private class TaskbarOverlayProxyView extends AbstractFloatingView {
187 
TaskbarOverlayProxyView()188         private TaskbarOverlayProxyView() {
189             super(mTaskbarContext, null);
190         }
191 
show()192         private void show() {
193             mIsOpen = true;
194             mTaskbarContext.getDragLayer().addView(this);
195         }
196 
197         @Override
handleClose(boolean animate)198         protected void handleClose(boolean animate) {
199             mTaskbarContext.getDragLayer().removeView(this);
200             Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
201         }
202 
203         @Override
isOfType(int type)204         protected boolean isOfType(int type) {
205             return (type & TYPE_TASKBAR_OVERLAY_PROXY) != 0;
206         }
207 
208         @Override
onControllerInterceptTouchEvent(MotionEvent ev)209         public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
210             return false;
211         }
212     }
213 }
214