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