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