1 /* 2 * Copyright (C) 2018 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.uioverrides.touchcontrollers; 17 18 import static android.view.MotionEvent.ACTION_CANCEL; 19 import static android.view.MotionEvent.ACTION_DOWN; 20 import static android.view.MotionEvent.ACTION_MOVE; 21 import static android.view.MotionEvent.ACTION_UP; 22 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; 23 24 import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll; 25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN; 26 27 import android.graphics.PointF; 28 import android.util.SparseArray; 29 import android.view.MotionEvent; 30 import android.view.ViewConfiguration; 31 import android.view.Window; 32 import android.view.WindowManager; 33 34 import com.android.launcher3.AbstractFloatingView; 35 import com.android.launcher3.DeviceProfile; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.LauncherState; 38 import com.android.launcher3.util.TouchController; 39 import com.android.quickstep.SystemUiProxy; 40 41 import java.io.PrintWriter; 42 43 /** 44 * TouchController for handling touch events that get sent to the StatusBar. Once the 45 * Once the event delta mDownY passes the touch slop, the events start getting forwarded. 46 * All events are offset by initial Y value of the pointer. 47 */ 48 public class StatusBarTouchController implements TouchController { 49 50 private static final String TAG = "StatusBarController"; 51 52 private final Launcher mLauncher; 53 private final SystemUiProxy mSystemUiProxy; 54 private final float mTouchSlop; 55 private int mLastAction; 56 private final SparseArray<PointF> mDownEvents; 57 58 /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/ 59 private boolean mCanIntercept; 60 61 private boolean mIsTrackpadReverseScroll; 62 StatusBarTouchController(Launcher l)63 public StatusBarTouchController(Launcher l) { 64 mLauncher = l; 65 mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher); 66 // Guard against TAPs by increasing the touch slop. 67 mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop(); 68 mDownEvents = new SparseArray<>(); 69 } 70 71 @Override dump(String prefix, PrintWriter writer)72 public void dump(String prefix, PrintWriter writer) { 73 writer.println(prefix + "mCanIntercept:" + mCanIntercept); 74 writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction)); 75 writer.println(prefix + "mSysUiProxy available:" 76 + SystemUiProxy.INSTANCE.get(mLauncher).isActive()); 77 } 78 dispatchTouchEvent(MotionEvent ev)79 private void dispatchTouchEvent(MotionEvent ev) { 80 if (mSystemUiProxy.isActive()) { 81 mLastAction = ev.getActionMasked(); 82 mSystemUiProxy.onStatusBarTouchEvent(ev); 83 } 84 } 85 86 @Override onControllerInterceptTouchEvent(MotionEvent ev)87 public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { 88 int action = ev.getActionMasked(); 89 int idx = ev.getActionIndex(); 90 int pid = ev.getPointerId(idx); 91 if (action == ACTION_DOWN) { 92 mCanIntercept = canInterceptTouch(ev); 93 if (!mCanIntercept) { 94 return false; 95 } 96 mDownEvents.clear(); 97 mDownEvents.put(pid, new PointF(ev.getX(), ev.getY())); 98 mIsTrackpadReverseScroll = !mLauncher.isNaturalScrollingEnabled() 99 && isTrackpadScroll(ev); 100 } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 101 // Check!! should only set it only when threshold is not entered. 102 mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx))); 103 } 104 if (!mCanIntercept) { 105 return false; 106 } 107 if (action == ACTION_MOVE && mDownEvents.contains(pid)) { 108 float dy = ev.getY(idx) - mDownEvents.get(pid).y; 109 float dx = ev.getX(idx) - mDownEvents.get(pid).x; 110 if (mIsTrackpadReverseScroll) { 111 dy = -dy; 112 } 113 // Currently input dispatcher will not do touch transfer if there are more than 114 // one touch pointer. Hence, even if slope passed, only set the slippery flag 115 // when there is single touch event. (context: InputDispatcher.cpp line 1445) 116 if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) { 117 ev.setAction(ACTION_DOWN); 118 dispatchTouchEvent(ev); 119 setWindowSlippery(true); 120 return true; 121 } 122 if (Math.abs(dx) > mTouchSlop) { 123 mCanIntercept = false; 124 } 125 } 126 return false; 127 } 128 129 @Override onControllerTouchEvent(MotionEvent ev)130 public final boolean onControllerTouchEvent(MotionEvent ev) { 131 int action = ev.getAction(); 132 if (action == ACTION_UP || action == ACTION_CANCEL) { 133 dispatchTouchEvent(ev); 134 mLauncher.getStatsLogManager().logger() 135 .log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN); 136 setWindowSlippery(false); 137 mIsTrackpadReverseScroll = false; 138 return true; 139 } 140 return true; 141 } 142 143 /** 144 * FLAG_SLIPPERY enables touches to slide out of a window into neighboring 145 * windows in mid-gesture instead of being captured for the duration of 146 * the gesture. 147 * 148 * This flag changes the behavior of touch focus for this window only. 149 * Touches can slide out of the window but they cannot necessarily slide 150 * back in (unless the other window with touch focus permits it). 151 */ setWindowSlippery(boolean enable)152 private void setWindowSlippery(boolean enable) { 153 Window w = mLauncher.getWindow(); 154 WindowManager.LayoutParams wlp = w.getAttributes(); 155 if (enable) { 156 wlp.flags |= FLAG_SLIPPERY; 157 } else { 158 wlp.flags &= ~FLAG_SLIPPERY; 159 } 160 w.setAttributes(wlp); 161 } 162 canInterceptTouch(MotionEvent ev)163 private boolean canInterceptTouch(MotionEvent ev) { 164 if (!mLauncher.isInState(LauncherState.NORMAL) || 165 AbstractFloatingView.getTopOpenViewWithType(mLauncher, 166 AbstractFloatingView.TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW) != null) { 167 return false; 168 } else { 169 // For NORMAL state, only listen if the event originated above the navbar height 170 DeviceProfile dp = mLauncher.getDeviceProfile(); 171 if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) { 172 return false; 173 } 174 } 175 return SystemUiProxy.INSTANCE.get(mLauncher).isActive(); 176 } 177 }