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