1 /*
2  * Copyright (C) 2019 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.quickstep.inputconsumers;
17 
18 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
19 
20 import android.media.AudioManager;
21 import android.media.session.MediaSessionManager;
22 import android.view.KeyEvent;
23 import android.view.MotionEvent;
24 import android.view.View;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.statemanager.BaseState;
31 import com.android.launcher3.testing.TestLogging;
32 import com.android.launcher3.testing.shared.TestProtocol;
33 import com.android.launcher3.views.BaseDragLayer;
34 import com.android.quickstep.BaseContainerInterface;
35 import com.android.quickstep.GestureState;
36 import com.android.quickstep.InputConsumer;
37 import com.android.quickstep.TaskUtils;
38 import com.android.quickstep.views.RecentsViewContainer;
39 import com.android.systemui.shared.system.InputMonitorCompat;
40 
41 /**
42  * Input consumer for handling touch on the recents/Launcher activity.
43  */
44 public class OverviewInputConsumer<S extends BaseState<S>, T extends RecentsViewContainer>
45         implements InputConsumer {
46 
47     private final T mContainer;
48     private final BaseContainerInterface<?, T> mContainerInterface;
49     private final BaseDragLayer mTarget;
50     private final InputMonitorCompat mInputMonitor;
51     private final GestureState mGestureState;
52 
53     private final int[] mLocationOnScreen = new int[2];
54 
55     private final boolean mStartingInActivityBounds;
56     private boolean mTargetHandledTouch;
57     private boolean mHasSetTouchModeForFirstDPadEvent;
58     private boolean mIsWaitingForAttachToWindow;
59 
OverviewInputConsumer(GestureState gestureState, T container, @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds)60     public OverviewInputConsumer(GestureState gestureState, T container,
61             @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
62         mContainer = container;
63         mInputMonitor = inputMonitor;
64         mStartingInActivityBounds = startingInActivityBounds;
65         mContainerInterface = gestureState.getContainerInterface();
66         mGestureState = gestureState;
67 
68         mTarget = container.getDragLayer();
69         mTarget.getLocationOnScreen(mLocationOnScreen);
70     }
71 
72     @Override
getType()73     public int getType() {
74         return TYPE_OVERVIEW;
75     }
76 
77     @Override
allowInterceptByParent()78     public boolean allowInterceptByParent() {
79         return !mTargetHandledTouch;
80     }
81 
82     @Override
onMotionEvent(MotionEvent ev)83     public void onMotionEvent(MotionEvent ev) {
84         int flags = ev.getEdgeFlags();
85         if (!mStartingInActivityBounds) {
86             ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
87         }
88         ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
89         boolean handled = false;
90         if (mGestureState == null || !mGestureState.isInExtendedSlopRegion()) {
91             handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
92         }
93         ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
94         ev.setEdgeFlags(flags);
95 
96         if (!mTargetHandledTouch && handled) {
97             mTargetHandledTouch = true;
98             if (!mStartingInActivityBounds) {
99                 mContainerInterface.closeOverlay();
100                 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
101             }
102             if (mInputMonitor != null) {
103                 TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
104                 mInputMonitor.pilferPointers();
105             }
106         }
107         if (mHasSetTouchModeForFirstDPadEvent) {
108             mContainer.getRootView().clearFocus();
109         }
110     }
111 
112     @Override
onHoverEvent(MotionEvent ev)113     public void onHoverEvent(MotionEvent ev) {
114         mContainer.dispatchGenericMotionEvent(ev);
115     }
116 
117     @Override
onKeyEvent(KeyEvent ev)118     public void onKeyEvent(KeyEvent ev) {
119         switch (ev.getKeyCode()) {
120             case KeyEvent.KEYCODE_VOLUME_DOWN:
121             case KeyEvent.KEYCODE_VOLUME_UP:
122             case KeyEvent.KEYCODE_VOLUME_MUTE:
123                 MediaSessionManager mgr = mContainer.asContext()
124                         .getSystemService(MediaSessionManager.class);
125                 mgr.dispatchVolumeKeyEventAsSystemService(ev,
126                         AudioManager.USE_DEFAULT_STREAM_TYPE);
127                 break;
128             case KeyEvent.KEYCODE_DPAD_LEFT:
129             case KeyEvent.KEYCODE_DPAD_RIGHT:
130                 if (mHasSetTouchModeForFirstDPadEvent) {
131                     break;
132                 }
133                 View viewRoot = mContainer.getRootView();
134                 if (viewRoot.isAttachedToWindow()) {
135                     setTouchModeChanged(viewRoot);
136                     break;
137                 }
138                 if (mIsWaitingForAttachToWindow) {
139                     break;
140                 }
141                 mIsWaitingForAttachToWindow = true;
142                 viewRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
143                     @Override
144                     public void onViewAttachedToWindow(View view) {
145                         view.removeOnAttachStateChangeListener(this);
146                         mIsWaitingForAttachToWindow = false;
147                         setTouchModeChanged(viewRoot);
148                     }
149 
150                     @Override
151                     public void onViewDetachedFromWindow(View view) {
152                         // Do nothing
153                     }
154                 });
155                 break;
156             default:
157                 break;
158         }
159         mContainer.dispatchKeyEvent(ev);
160     }
161 
setTouchModeChanged(@onNull View viewRoot)162     private void setTouchModeChanged(@NonNull View viewRoot) {
163         // When Overview is launched via meta+tab or swipe up from an app, the touch
164         // mode somehow is not changed to false by the Android framework. The
165         // subsequent key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched
166         // to focused views, while focus can only be requested in
167         // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
168         // note, here we launch overview with live tile.
169         mHasSetTouchModeForFirstDPadEvent = true;
170         viewRoot.getViewRootImpl().touchModeChanged(false);
171     }
172 }
173 
174