• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.quickstep;
18 
19 import static android.view.MotionEvent.ACTION_CANCEL;
20 import static android.view.MotionEvent.ACTION_DOWN;
21 import static android.view.MotionEvent.ACTION_MOVE;
22 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
23 import static android.view.MotionEvent.ACTION_UP;
24 
25 import static com.android.launcher3.states.RotationHelper.deltaRotation;
26 
27 import android.content.res.Resources;
28 import android.graphics.Point;
29 import android.graphics.RectF;
30 import android.util.Log;
31 import android.view.MotionEvent;
32 import android.view.Surface;
33 
34 import com.android.launcher3.R;
35 import com.android.launcher3.testing.shared.ResourceUtils;
36 import com.android.launcher3.testing.shared.TestProtocol;
37 import com.android.launcher3.util.DisplayController.Info;
38 import com.android.launcher3.util.NavigationMode;
39 import com.android.launcher3.util.window.CachedDisplayInfo;
40 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
41 import com.android.systemui.shared.Flags;
42 
43 import java.io.PrintWriter;
44 import java.util.HashMap;
45 import java.util.Map;
46 
47 /**
48  * Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
49  * See {@link OrientationRectF#applyTransformToRotation(MotionEvent, int, boolean)} for
50  * transformation of MotionEvents from one orientation's coordinate space to another's.
51  *
52  * This class only supports single touch/pointer gesture tracking for touches started in a supported
53  * nav bar region.
54  */
55 class OrientationTouchTransformer {
56 
57     private static final String TAG = "OrientationTouchTransformer";
58     private static final boolean DEBUG = false;
59 
60     private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
61 
62     private final Map<CachedDisplayInfo, OrientationRectF> mSwipeTouchRegions =
63             new HashMap<CachedDisplayInfo, OrientationRectF>();
64     private final RectF mAssistantLeftRegion = new RectF();
65     private final RectF mAssistantRightRegion = new RectF();
66     private final RectF mOneHandedModeRegion = new RectF();
67     private CachedDisplayInfo mCachedDisplayInfo = new CachedDisplayInfo();
68     private int mNavBarGesturalHeight;
69     private final int mNavBarLargerGesturalHeight;
70     private boolean mEnableMultipleRegions;
71     private Resources mResources;
72     private OrientationRectF mLastRectTouched;
73     /**
74      * The rotation of the last touched nav bar, whether that be through the last region the user
75      * touched down on or valid rotation user turned their device to.
76      * Note this is different than
77      * {@link #mQuickStepStartingRotation} as it always updates its value on every touch whereas
78      * mQuickstepStartingRotation only updates when device rotation matches touch rotation.
79      */
80     private int mActiveTouchRotation;
81     private NavigationMode mMode;
82     private QuickStepContractInfo mContractInfo;
83 
84     /**
85      * Represents if we're currently in a swipe "session" of sorts. If value is
86      * QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
87      * Otherwise it will be the rotation of the display when the user first interacted with the
88      * active nav bar region.
89      * The "session" ends when {@link #enableMultipleRegions(boolean, Info)} is
90      * called - usually from a timeout or if user starts interacting w/ the foreground app.
91      *
92      * This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
93      * the rect is purely used for tracking touch interactions and usually this "session" will
94      * outlast the touch interaction.
95      */
96     private int mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
97 
98     /** For testability */
99     interface QuickStepContractInfo {
getWindowCornerRadius()100         float getWindowCornerRadius();
101     }
102 
103 
OrientationTouchTransformer(Resources resources, NavigationMode mode, QuickStepContractInfo contractInfo)104     OrientationTouchTransformer(Resources resources, NavigationMode mode,
105             QuickStepContractInfo contractInfo) {
106         mResources = resources;
107         mMode = mode;
108         mContractInfo = contractInfo;
109         mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
110         mNavBarLargerGesturalHeight = ResourceUtils.getDimenByName(
111                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_LARGER_SIZE, resources,
112                 mNavBarGesturalHeight);
113     }
114 
refreshTouchRegion(Info info, Resources newRes, String reason)115     private void refreshTouchRegion(Info info, Resources newRes, String reason) {
116         // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
117         // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
118         // It tries to cache and reuse swipe regions whenever possible based only on rotation
119         mResources = newRes;
120         mSwipeTouchRegions.clear();
121         resetSwipeRegions(info, reason);
122     }
123 
setNavigationMode(NavigationMode newMode, Info info, Resources newRes)124     void setNavigationMode(NavigationMode newMode, Info info, Resources newRes) {
125         if (enableLog()) {
126             Log.d(TAG, "setNavigationMode new: " + newMode + " oldMode: " + mMode + " " + this);
127         }
128         if (mMode == newMode) {
129             return;
130         }
131         this.mMode = newMode;
132         refreshTouchRegion(info, newRes, "setNavigationMode");
133     }
134 
setGesturalHeight(int newGesturalHeight, Info info, Resources newRes)135     void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
136         if (mNavBarGesturalHeight == newGesturalHeight) {
137             return;
138         }
139         mNavBarGesturalHeight = newGesturalHeight;
140         refreshTouchRegion(info, newRes, "setGesturalHeight");
141     }
142 
143     /**
144      * Sets the current nav bar region to listen to events for as determined by
145      * {@param info}. If multiple nav bar regions are enabled, then this region will be added
146      * alongside other regions.
147      * Ok to call multiple times
148      *
149      * @see #enableMultipleRegions(boolean, Info)
150      */
createOrAddTouchRegion(Info info, String reason)151     void createOrAddTouchRegion(Info info, String reason) {
152         mCachedDisplayInfo = new CachedDisplayInfo(info.currentSize, info.rotation);
153 
154         if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
155                 && mCachedDisplayInfo.rotation == mQuickStepStartingRotation) {
156             // User already was swiping and the current screen is same rotation as the starting one
157             // Remove active nav bars in other rotations except for the one we started out in
158             resetSwipeRegions(info, reason);
159             return;
160         }
161         OrientationRectF region = mSwipeTouchRegions.get(mCachedDisplayInfo);
162         if (region != null) {
163             return;
164         }
165 
166         if (mEnableMultipleRegions) {
167             mSwipeTouchRegions.put(mCachedDisplayInfo, createRegionForDisplay(info, reason));
168         } else {
169             resetSwipeRegions(info, reason);
170         }
171     }
172 
173     /**
174      * Call when we want to start tracking nav bar touch regions in multiple orientations.
175      * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
176      *
177      * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
178      * @param info The current displayInfo which will be the start of the quickswitch gesture
179      */
enableMultipleRegions(boolean enableMultipleRegions, Info info)180     void enableMultipleRegions(boolean enableMultipleRegions, Info info) {
181         mEnableMultipleRegions = enableMultipleRegions && mMode != NavigationMode.TWO_BUTTONS;
182         if (mEnableMultipleRegions) {
183             mQuickStepStartingRotation = info.rotation;
184         } else {
185             mActiveTouchRotation = 0;
186             mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
187         }
188         resetSwipeRegions(info, "enableMultipleRegions");
189     }
190 
191     /**
192      * Call when removing multiple regions to swipe from, but still in active quickswitch mode (task
193      * list is still frozen).
194      * Ex. This would be called when user has quickswitched to the same app rotation that
195      * they started quickswitching in, indicating that extra nav regions can be ignored. Calling
196      * this will update the value of {@link #mActiveTouchRotation}
197      *
198      * @param displayInfo The display whos rotation will be used as the current active rotation
199      */
setSingleActiveRegion(Info displayInfo)200     void setSingleActiveRegion(Info displayInfo) {
201         mActiveTouchRotation = displayInfo.rotation;
202         resetSwipeRegions(displayInfo, "setSingleActiveRegion");
203     }
204 
205     /**
206      * Only saves the swipe region represented by {@param region}, clears the
207      * rest from {@link #mSwipeTouchRegions}
208      * To be called whenever we want to stop tracking more than one swipe region.
209      * Ok to call multiple times.
210      */
resetSwipeRegions(Info region, String reason)211     private void resetSwipeRegions(Info region, String reason) {
212         if (enableLog()) {
213             Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation
214                     + " reason=" + reason);
215         }
216 
217         mCachedDisplayInfo = new CachedDisplayInfo(region.currentSize, region.rotation);
218         OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo);
219         if (regionToKeep == null) {
220             regionToKeep = createRegionForDisplay(region, reason);
221         }
222         mSwipeTouchRegions.clear();
223         mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
224         updateAssistantRegions(regionToKeep);
225         updateOneHandedRegions(regionToKeep);
226     }
227 
resetSwipeRegions()228     private void resetSwipeRegions() {
229         OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo);
230         mSwipeTouchRegions.clear();
231         if (regionToKeep != null) {
232             mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
233             updateAssistantRegions(regionToKeep);
234             updateOneHandedRegions(regionToKeep);
235         }
236     }
237 
createRegionForDisplay(Info display, String reason)238     private OrientationRectF createRegionForDisplay(Info display, String reason) {
239         if (enableLog()) {
240             Log.d(TAG, "creating rotation region for: " + mCachedDisplayInfo.rotation
241             + " with mode: " + mMode + " displayRotation: " + display.rotation +
242                     " displaySize: " + display.currentSize +
243                     " navBarHeight: " + mNavBarGesturalHeight +
244                     " reason: " + reason);
245         }
246 
247         Point size = display.currentSize;
248         int rotation = display.rotation;
249         int touchHeight = mNavBarGesturalHeight;
250         OrientationRectF orientationRectF = new OrientationRectF(0, 0, size.x, size.y, rotation);
251         if (mMode == NavigationMode.NO_BUTTON
252                 || (mMode == NavigationMode.THREE_BUTTONS && Flags.threeButtonCornerSwipe())) {
253             orientationRectF.top = orientationRectF.bottom - touchHeight;
254             updateAssistantRegions(orientationRectF);
255         } else {
256             mAssistantLeftRegion.setEmpty();
257             mAssistantRightRegion.setEmpty();
258             int navbarSize = getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
259             switch (rotation) {
260                 case Surface.ROTATION_90:
261                     orientationRectF.left = orientationRectF.right
262                             - navbarSize;
263                     break;
264                 case Surface.ROTATION_270:
265                     orientationRectF.right = orientationRectF.left
266                             + navbarSize;
267                     break;
268                 default:
269                     orientationRectF.top = orientationRectF.bottom - touchHeight;
270             }
271         }
272         updateOneHandedRegions(orientationRectF);
273         ActiveGestureProtoLogProxy.logCreateTouchRegionForDisplay(rotation, size, orientationRectF,
274                 mOneHandedModeRegion, mNavBarGesturalHeight, mNavBarLargerGesturalHeight,
275                 reason);
276 
277         return orientationRectF;
278     }
279 
updateAssistantRegions(OrientationRectF orientationRectF)280     private void updateAssistantRegions(OrientationRectF orientationRectF) {
281         int navbarHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
282         int assistantWidth = mResources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
283         float assistantHeight = Math.max(navbarHeight, mContractInfo.getWindowCornerRadius());
284         mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
285         mAssistantLeftRegion.top = mAssistantRightRegion.top =
286                 orientationRectF.bottom - assistantHeight;
287 
288         mAssistantLeftRegion.left = 0;
289         mAssistantLeftRegion.right = assistantWidth;
290 
291         mAssistantRightRegion.right = orientationRectF.right;
292         mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
293     }
294 
updateOneHandedRegions(OrientationRectF orientationRectF)295     private void updateOneHandedRegions(OrientationRectF orientationRectF) {
296         // One handed gestural only active on portrait mode
297         mOneHandedModeRegion.set(0, orientationRectF.bottom - mNavBarLargerGesturalHeight,
298                 orientationRectF.right, orientationRectF.bottom);
299     }
300 
touchInAssistantRegion(MotionEvent ev)301     boolean touchInAssistantRegion(MotionEvent ev) {
302         return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
303                 || mAssistantRightRegion.contains(ev.getX(), ev.getY());
304 
305     }
306 
touchInOneHandedModeRegion(MotionEvent ev)307     boolean touchInOneHandedModeRegion(MotionEvent ev) {
308         return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
309     }
310 
getNavbarSize(String resName)311     private int getNavbarSize(String resName) {
312         return ResourceUtils.getNavbarSize(resName, mResources);
313     }
314 
touchInValidSwipeRegions(float x, float y)315     boolean touchInValidSwipeRegions(float x, float y) {
316         if (enableLog()) {
317             Log.d(TAG, "touchInValidSwipeRegions " + x + "," + y + " in " + mLastRectTouched);
318         }
319         if (mLastRectTouched != null) {
320             return mLastRectTouched.contains(x, y);
321         }
322         return false;
323     }
324 
getCurrentActiveRotation()325     int getCurrentActiveRotation() {
326         return mActiveTouchRotation;
327     }
328 
getQuickStepStartingRotation()329     int getQuickStepStartingRotation() {
330         return mQuickStepStartingRotation;
331     }
332 
transform(MotionEvent event)333     public void transform(MotionEvent event) {
334         int eventAction = event.getActionMasked();
335         switch (eventAction) {
336             case ACTION_MOVE: {
337                 if (mLastRectTouched == null) {
338                     return;
339                 }
340                 if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
341                     if (event.getSurfaceRotation() != mActiveTouchRotation) {
342                         // With Shell transitions, we should rotated to the orientation at the start
343                         // of the gesture not the current display rotation which will happen early
344                         mLastRectTouched.applyTransform(event,
345                                 deltaRotation(event.getSurfaceRotation(), mActiveTouchRotation),
346                                 true);
347                     }
348                 } else {
349                     mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation,
350                             true);
351                 }
352                 break;
353             }
354             case ACTION_CANCEL:
355             case ACTION_UP: {
356                 if (mLastRectTouched == null) {
357                     return;
358                 }
359                 if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
360                     if (event.getSurfaceRotation() != mActiveTouchRotation) {
361                         // With Shell transitions, we should rotated to the orientation at the start
362                         // of the gesture not the current display rotation which will happen early
363                         mLastRectTouched.applyTransform(event,
364                                 deltaRotation(event.getSurfaceRotation(), mActiveTouchRotation),
365                                 true);
366                     }
367                 } else {
368                     mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation,
369                             true);
370                 }
371                 mLastRectTouched = null;
372                 break;
373             }
374             case ACTION_POINTER_DOWN:
375             case ACTION_DOWN: {
376                 if (enableLog()) {
377                     Log.d(TAG, "ACTION_DOWN mLastRectTouched: " + mLastRectTouched);
378                 }
379                 if (mLastRectTouched != null) {
380                     return;
381                 }
382 
383                 for (OrientationRectF rect : mSwipeTouchRegions.values()) {
384                     if (enableLog()) {
385                         Log.d(TAG, "ACTION_DOWN rect: " + rect);
386                     }
387                     if (rect == null) {
388                         continue;
389                     }
390                     if (rect.applyTransformFromRotation(
391                             event, mCachedDisplayInfo.rotation, false)) {
392                         mLastRectTouched = rect;
393                         mActiveTouchRotation = rect.getRotation();
394                         if (mEnableMultipleRegions
395                                 && mCachedDisplayInfo.rotation == mActiveTouchRotation) {
396                             // TODO(b/154580671) might make this block unnecessary
397                             // Start a touch session for the default nav region for the display
398                             mQuickStepStartingRotation = mLastRectTouched.getRotation();
399                             resetSwipeRegions();
400                         }
401                         if (enableLog()) {
402                             Log.d(TAG, "set active region: " + rect);
403                         }
404                         return;
405                     }
406                 }
407                 break;
408             }
409         }
410     }
411 
enableLog()412     private boolean enableLog() {
413         return DEBUG || TestProtocol.sDebugTracing;
414     }
415 
dump(PrintWriter pw)416     public void dump(PrintWriter pw) {
417         pw.println("OrientationTouchTransformerState: ");
418         pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
419         pw.println("  lastTouchedRegion=" + mLastRectTouched);
420         pw.println("  multipleRegionsEnabled=" + mEnableMultipleRegions);
421         StringBuilder regions = new StringBuilder("  currentTouchableRotations=");
422         for (CachedDisplayInfo key: mSwipeTouchRegions.keySet()) {
423             OrientationRectF rectF = mSwipeTouchRegions.get(key);
424             regions.append(rectF).append(" ");
425         }
426         pw.println(regions);
427         pw.println("  mNavBarGesturalHeight=" + mNavBarGesturalHeight);
428         pw.println("  mNavBarLargerGesturalHeight=" + mNavBarLargerGesturalHeight);
429         pw.println("  mAssistantLeftRegion=" + mAssistantLeftRegion);
430         pw.println("  mAssistantRightRegion=" + mAssistantRightRegion);
431         pw.println("  mOneHandedModeRegion=" + mOneHandedModeRegion);
432     }
433 }
434