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