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 StatusBarTouchController(Launcher l)61 public StatusBarTouchController(Launcher l) { 62 mLauncher = l; 63 mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher); 64 // Guard against TAPs by increasing the touch slop. 65 mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop(); 66 mDownEvents = new SparseArray<>(); 67 } 68 69 @Override dump(String prefix, PrintWriter writer)70 public void dump(String prefix, PrintWriter writer) { 71 writer.println(prefix + "mCanIntercept:" + mCanIntercept); 72 writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction)); 73 writer.println(prefix + "mSysUiProxy available:" 74 + SystemUiProxy.INSTANCE.get(mLauncher).isActive()); 75 } 76 dispatchTouchEvent(MotionEvent ev)77 private void dispatchTouchEvent(MotionEvent ev) { 78 if (mSystemUiProxy.isActive()) { 79 mLastAction = ev.getActionMasked(); 80 mSystemUiProxy.onStatusBarTouchEvent(ev); 81 } 82 } 83 84 @Override onControllerInterceptTouchEvent(MotionEvent ev)85 public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { 86 int action = ev.getActionMasked(); 87 int idx = ev.getActionIndex(); 88 int pid = ev.getPointerId(idx); 89 if (action == ACTION_DOWN) { 90 mCanIntercept = canInterceptTouch(ev); 91 if (!mCanIntercept) { 92 return false; 93 } 94 mDownEvents.clear(); 95 mDownEvents.put(pid, new PointF(ev.getX(), ev.getY())); 96 } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 97 // Check!! should only set it only when threshold is not entered. 98 mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx))); 99 } 100 if (!mCanIntercept) { 101 return false; 102 } 103 if (action == ACTION_MOVE && mDownEvents.contains(pid)) { 104 float dy = ev.getY(idx) - mDownEvents.get(pid).y; 105 float dx = ev.getX(idx) - mDownEvents.get(pid).x; 106 // Currently input dispatcher will not do touch transfer if there are more than 107 // one touch pointer. Hence, even if slope passed, only set the slippery flag 108 // when there is single touch event. (context: InputDispatcher.cpp line 1445) 109 if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) { 110 ev.setAction(ACTION_DOWN); 111 dispatchTouchEvent(ev); 112 setWindowSlippery(true); 113 return true; 114 } 115 if (Math.abs(dx) > mTouchSlop) { 116 mCanIntercept = false; 117 } 118 } 119 return false; 120 } 121 122 @Override onControllerTouchEvent(MotionEvent ev)123 public final boolean onControllerTouchEvent(MotionEvent ev) { 124 int action = ev.getAction(); 125 if (action == ACTION_UP || action == ACTION_CANCEL) { 126 dispatchTouchEvent(ev); 127 mLauncher.getStatsLogManager().logger() 128 .log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN); 129 setWindowSlippery(false); 130 return true; 131 } 132 return true; 133 } 134 135 /** 136 * FLAG_SLIPPERY enables touches to slide out of a window into neighboring 137 * windows in mid-gesture instead of being captured for the duration of 138 * the gesture. 139 * 140 * This flag changes the behavior of touch focus for this window only. 141 * Touches can slide out of the window but they cannot necessarily slide 142 * back in (unless the other window with touch focus permits it). 143 */ setWindowSlippery(boolean enable)144 private void setWindowSlippery(boolean enable) { 145 Window w = mLauncher.getWindow(); 146 WindowManager.LayoutParams wlp = w.getAttributes(); 147 if (enable) { 148 wlp.flags |= FLAG_SLIPPERY; 149 } else { 150 wlp.flags &= ~FLAG_SLIPPERY; 151 } 152 w.setAttributes(wlp); 153 } 154 canInterceptTouch(MotionEvent ev)155 private boolean canInterceptTouch(MotionEvent ev) { 156 if (isTrackpadScroll(ev) || !mLauncher.isInState(LauncherState.NORMAL) 157 || AbstractFloatingView.getTopOpenViewWithType(mLauncher, 158 AbstractFloatingView.TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW) != null) { 159 return false; 160 } else { 161 // For NORMAL state, only listen if the event originated above the navbar height 162 DeviceProfile dp = mLauncher.getDeviceProfile(); 163 if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) { 164 return false; 165 } 166 } 167 return SystemUiProxy.INSTANCE.get(mLauncher).isActive(); 168 } 169 }