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.KeyEvent.ACTION_UP; 19 import static android.view.KeyEvent.KEYCODE_BACK; 20 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; 21 22 import android.content.Context; 23 import android.graphics.Insets; 24 import android.media.permission.SafeCloseable; 25 import android.view.KeyEvent; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.ViewTreeObserver; 29 import android.view.WindowInsets; 30 31 import androidx.annotation.NonNull; 32 33 import com.android.app.viewcapture.ViewCaptureFactory; 34 import com.android.launcher3.AbstractFloatingView; 35 import com.android.launcher3.testing.TestLogging; 36 import com.android.launcher3.testing.shared.TestProtocol; 37 import com.android.launcher3.util.DisplayController; 38 import com.android.launcher3.util.TouchController; 39 import com.android.launcher3.views.BaseDragLayer; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** Root drag layer for the Taskbar overlay window. */ 45 public class TaskbarOverlayDragLayer extends 46 BaseDragLayer<TaskbarOverlayContext> implements 47 ViewTreeObserver.OnComputeInternalInsetsListener { 48 49 private SafeCloseable mViewCaptureCloseable; 50 private final List<TouchController> mTouchControllers = new ArrayList<>(); 51 TaskbarOverlayDragLayer(Context context)52 TaskbarOverlayDragLayer(Context context) { 53 super(context, null, 1); 54 setClipChildren(false); 55 recreateControllers(); 56 } 57 58 @Override onAttachedToWindow()59 protected void onAttachedToWindow() { 60 super.onAttachedToWindow(); 61 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 62 mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext()) 63 .startCapture(getRootView(), ".TaskbarOverlay"); 64 } 65 66 @Override onDetachedFromWindow()67 protected void onDetachedFromWindow() { 68 super.onDetachedFromWindow(); 69 getViewTreeObserver().removeOnComputeInternalInsetsListener(this); 70 mViewCaptureCloseable.close(); 71 } 72 73 @Override recreateControllers()74 public void recreateControllers() { 75 List<TouchController> controllers = new ArrayList<>(); 76 controllers.add(mActivity.getDragController()); 77 controllers.addAll(mTouchControllers); 78 mControllers = controllers.toArray(new TouchController[0]); 79 } 80 81 @Override dispatchTouchEvent(MotionEvent ev)82 public boolean dispatchTouchEvent(MotionEvent ev) { 83 TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); 84 return super.dispatchTouchEvent(ev); 85 } 86 87 @Override dispatchKeyEvent(KeyEvent event)88 public boolean dispatchKeyEvent(KeyEvent event) { 89 if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) { 90 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); 91 if (topView != null && topView.canHandleBack()) { 92 topView.onBackInvoked(); 93 return true; 94 } 95 } else if (event.getAction() == KeyEvent.ACTION_DOWN 96 && event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) { 97 // Ignore escape if pressed in conjunction with any modifier keys. Close each 98 // floating view one at a time for each key press. 99 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); 100 if (topView != null) { 101 topView.close(/* animate= */ true); 102 return true; 103 } 104 } 105 return super.dispatchKeyEvent(event); 106 } 107 108 @Override onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)109 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { 110 if (mActivity.isAnySystemDragInProgress()) { 111 inoutInfo.touchableRegion.setEmpty(); 112 inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); 113 } 114 } 115 116 @Override onApplyWindowInsets(WindowInsets insets)117 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 118 insets = updateInsetsDueToStashing(insets); 119 setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect()); 120 return insets; 121 } 122 123 @Override onViewRemoved(View child)124 public void onViewRemoved(View child) { 125 super.onViewRemoved(child); 126 mActivity.getOverlayController().maybeCloseWindow(); 127 } 128 129 /** Adds a {@link TouchController} to this drag layer. */ addTouchController(@onNull TouchController touchController)130 public void addTouchController(@NonNull TouchController touchController) { 131 mTouchControllers.add(touchController); 132 recreateControllers(); 133 } 134 135 /** Removes a {@link TouchController} from this drag layer. */ removeTouchController(@onNull TouchController touchController)136 public void removeTouchController(@NonNull TouchController touchController) { 137 mTouchControllers.remove(touchController); 138 recreateControllers(); 139 } 140 141 /** 142 * Taskbar automatically stashes when opening all apps, but we don't report the insets as 143 * changing to avoid moving the underlying app. But internally, the apps view should still 144 * layout according to the stashed insets rather than the unstashed insets. So this method 145 * does two things: 146 * 1) Sets navigationBars bottom inset to stashedHeight. 147 * 2) Sets tappableInsets bottom inset to 0. 148 */ updateInsetsDueToStashing(WindowInsets oldInsets)149 private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) { 150 if (!DisplayController.isTransientTaskbar(mActivity)) { 151 return oldInsets; 152 } 153 WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets); 154 155 Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars()); 156 Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right, 157 mActivity.getStashedTaskbarHeight()); 158 updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets); 159 160 Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement()); 161 Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top, 162 oldTappableInsets.right, 0); 163 updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets); 164 165 return updatedInsetsBuilder.build(); 166 } 167 } 168