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