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