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;
17 
18 import static android.content.Intent.ACTION_USER_UNLOCKED;
19 import static android.view.Surface.ROTATION_0;
20 
21 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
22 import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
23 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
24 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
25 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
26 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
36 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
37 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
38 
39 import android.app.ActivityManager;
40 import android.content.BroadcastReceiver;
41 import android.content.ComponentName;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.content.res.Resources;
46 import android.graphics.Region;
47 import android.os.Process;
48 import android.os.UserManager;
49 import android.provider.Settings;
50 import android.text.TextUtils;
51 import android.view.MotionEvent;
52 import android.view.OrientationEventListener;
53 
54 import androidx.annotation.BinderThread;
55 
56 import com.android.launcher3.R;
57 import com.android.launcher3.Utilities;
58 import com.android.launcher3.testing.TestProtocol;
59 import com.android.launcher3.util.DefaultDisplay;
60 import com.android.launcher3.util.SecureSettingsObserver;
61 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
62 import com.android.quickstep.util.NavBarPosition;
63 import com.android.quickstep.util.RecentsOrientedState;
64 import com.android.systemui.shared.system.ActivityManagerWrapper;
65 import com.android.systemui.shared.system.QuickStepContract;
66 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
67 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
68 import com.android.systemui.shared.system.TaskStackChangeListener;
69 
70 import java.io.PrintWriter;
71 import java.util.ArrayList;
72 import java.util.List;
73 import java.util.stream.Collectors;
74 
75 /**
76  * Manages the state of the system during a swipe up gesture.
77  */
78 public class RecentsAnimationDeviceState implements
79         NavigationModeChangeListener,
80         DefaultDisplay.DisplayInfoChangeListener {
81 
82     private final Context mContext;
83     private final SysUINavigationMode mSysUiNavMode;
84     private final DefaultDisplay mDefaultDisplay;
85     private final int mDisplayId;
86     private int mDisplayRotation;
87 
88     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
89 
90     private @SystemUiStateFlags int mSystemUiStateFlags;
91     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
92     private NavBarPosition mNavBarPosition;
93 
94     private final Region mDeferredGestureRegion = new Region();
95     private boolean mAssistantAvailable;
96     private float mAssistantVisibility;
97 
98     private boolean mIsUserUnlocked;
99     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
100     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
101         @Override
102         public void onReceive(Context context, Intent intent) {
103             if (ACTION_USER_UNLOCKED.equals(intent.getAction())) {
104                 mIsUserUnlocked = true;
105                 notifyUserUnlocked();
106             }
107         }
108     };
109 
110     private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
111         @Override
112         public void onRecentTaskListFrozenChanged(boolean frozen) {
113             mTaskListFrozen = frozen;
114             if (frozen || mInOverview) {
115                 return;
116             }
117             enableMultipleRegions(false);
118         }
119 
120         @Override
121         public void onActivityRotation(int displayId) {
122             super.onActivityRotation(displayId);
123             // This always gets called before onDisplayInfoChanged() so we know how to process
124             // the rotation in that method. This is done to avoid having a race condition between
125             // the sensor readings and onDisplayInfoChanged() call
126             if (displayId != mDisplayId) {
127                 return;
128             }
129 
130             mPrioritizeDeviceRotation = true;
131             if (mInOverview) {
132                 // reset, launcher must be rotating
133                 mExitOverviewRunnable.run();
134             }
135         }
136     };
137 
138     private Runnable mExitOverviewRunnable = new Runnable() {
139         @Override
140         public void run() {
141             mInOverview = false;
142             enableMultipleRegions(false);
143         }
144     };
145 
146     private OrientationTouchTransformer mOrientationTouchTransformer;
147     /**
148      * Used to listen for when the device rotates into the orientation of the current
149      * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
150      * app and then rotates rotates the device to match that orientation, this triggers calls to
151      * sysui to adjust the navbar.
152      */
153     private OrientationEventListener mOrientationListener;
154     private int mSensorRotation = ROTATION_0;
155     /**
156      * This is the configuration of the foreground app or the app that will be in the foreground
157      * once a quickstep gesture finishes.
158      */
159     private int mCurrentAppRotation = -1;
160     /**
161      * This flag is set to true when the device physically changes orientations. When true,
162      * we will always report the current rotation of the foreground app whenever the display
163      * changes, as it would indicate the user's intention to rotate the foreground app.
164      */
165     private boolean mPrioritizeDeviceRotation = false;
166 
167     private Region mExclusionRegion;
168     private SystemGestureExclusionListenerCompat mExclusionListener;
169 
170     private final List<ComponentName> mGestureBlockedActivities;
171     private Runnable mOnDestroyFrozenTaskRunnable;
172     /**
173      * Set to true when user swipes to recents. In recents, we ignore the state of the recents
174      * task list being frozen or not to allow the user to keep interacting with nav bar rotation
175      * they went into recents with as opposed to defaulting to the default display rotation.
176      * TODO: (b/156984037) For when user rotates after entering overview
177      */
178     private boolean mInOverview;
179     private boolean mTaskListFrozen;
180 
181     private boolean mIsUserSetupComplete;
182 
RecentsAnimationDeviceState(Context context)183     public RecentsAnimationDeviceState(Context context) {
184         mContext = context;
185         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
186         mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
187         mDisplayId = mDefaultDisplay.getInfo().id;
188         runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
189 
190         // Register for user unlocked if necessary
191         mIsUserUnlocked = context.getSystemService(UserManager.class)
192                 .isUserUnlocked(Process.myUserHandle());
193         if (!mIsUserUnlocked) {
194             mContext.registerReceiver(mUserUnlockedReceiver,
195                     new IntentFilter(ACTION_USER_UNLOCKED));
196         }
197         runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver));
198 
199         // Register for exclusion updates
200         mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
201             @Override
202             @BinderThread
203             public void onExclusionChanged(Region region) {
204                 // Assignments are atomic, it should be safe on binder thread
205                 mExclusionRegion = region;
206             }
207         };
208         runOnDestroy(mExclusionListener::unregister);
209 
210         Resources resources = mContext.getResources();
211         mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
212                 () -> QuickStepContract.getWindowCornerRadius(resources));
213 
214         // Register for navigation mode changes
215         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
216         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
217 
218         // Add any blocked activities
219         String[] blockingActivities;
220         try {
221             blockingActivities =
222                     context.getResources().getStringArray(R.array.gesture_blocking_activities);
223         } catch (Resources.NotFoundException e) {
224             blockingActivities = new String[0];
225         }
226         mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
227         for (String blockingActivity : blockingActivities) {
228             if (!TextUtils.isEmpty(blockingActivity)) {
229                 mGestureBlockedActivities.add(
230                         ComponentName.unflattenFromString(blockingActivity));
231             }
232         }
233 
234         SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
235                 context.getContentResolver(),
236                 e -> mIsUserSetupComplete = e,
237                 Settings.Secure.USER_SETUP_COMPLETE,
238                 0);
239         mIsUserSetupComplete = userSetupObserver.getValue();
240         if (!mIsUserSetupComplete) {
241             userSetupObserver.register();
242             runOnDestroy(userSetupObserver::unregister);
243         }
244 
245         mOrientationListener = new OrientationEventListener(context) {
246             @Override
247             public void onOrientationChanged(int degrees) {
248                 int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
249                         mSensorRotation);
250                 if (newRotation == mSensorRotation) {
251                     return;
252                 }
253 
254                 mSensorRotation = newRotation;
255                 mPrioritizeDeviceRotation = true;
256 
257                 if (newRotation == mCurrentAppRotation) {
258                     // When user rotates device to the orientation of the foreground app after
259                     // quickstepping
260                     toggleSecondaryNavBarsForRotation();
261                 }
262             }
263         };
264     }
265 
setupOrientationSwipeHandler()266     private void setupOrientationSwipeHandler() {
267         ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
268         mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
269                 .unregisterTaskStackListener(mFrozenTaskListener);
270         runOnDestroy(mOnDestroyFrozenTaskRunnable);
271     }
272 
destroyOrientationSwipeHandlerCallback()273     private void destroyOrientationSwipeHandlerCallback() {
274         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
275         mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
276     }
277 
runOnDestroy(Runnable action)278     private void runOnDestroy(Runnable action) {
279         mOnDestroyActions.add(action);
280     }
281 
282     /**
283      * Cleans up all the registered listeners and receivers.
284      */
destroy()285     public void destroy() {
286         for (Runnable r : mOnDestroyActions) {
287             r.run();
288         }
289     }
290 
291     /**
292      * Adds a listener for the nav mode change, guaranteed to be called after the device state's
293      * mode has changed.
294      */
addNavigationModeChangedCallback(NavigationModeChangeListener listener)295     public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) {
296         listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener));
297         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
298     }
299 
300     @Override
onNavigationModeChanged(SysUINavigationMode.Mode newMode)301     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
302         mDefaultDisplay.removeChangeListener(this);
303         mDefaultDisplay.addChangeListener(this);
304         onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
305 
306         if (newMode == NO_BUTTON) {
307             mExclusionListener.register();
308         } else {
309             mExclusionListener.unregister();
310         }
311 
312         mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
313 
314         mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
315         if (!mMode.hasGestures && newMode.hasGestures) {
316             setupOrientationSwipeHandler();
317         } else if (mMode.hasGestures && !newMode.hasGestures){
318             destroyOrientationSwipeHandlerCallback();
319         }
320 
321         mMode = newMode;
322     }
323 
324     @Override
onDisplayInfoChanged(DefaultDisplay.Info info, int flags)325     public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
326         if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
327             // ignore displays that aren't running launcher and frame refresh rate changes
328             return;
329         }
330 
331         mDisplayRotation = info.rotation;
332 
333         if (!mMode.hasGestures) {
334             return;
335         }
336         mNavBarPosition = new NavBarPosition(mMode, info);
337         updateGestureTouchRegions();
338         mOrientationTouchTransformer.createOrAddTouchRegion(info);
339         mCurrentAppRotation = mDisplayRotation;
340 
341         /* Update nav bars on the following:
342          * a) if this is coming from an activity rotation OR
343          *   aa) we launch an app in the orientation that user is already in
344          * b) We're not in overview, since overview will always be portrait (w/o home rotation)
345          * c) We're actively in quickswitch mode
346          */
347         if ((mPrioritizeDeviceRotation
348                 || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
349                 && !mInOverview
350                 && mTaskListFrozen) {
351             toggleSecondaryNavBarsForRotation();
352         }
353     }
354 
355     /**
356      * @return the current navigation mode for the device.
357      */
getNavMode()358     public SysUINavigationMode.Mode getNavMode() {
359         return mMode;
360     }
361 
362     /**
363      * @return the nav bar position for the current nav bar mode and display rotation.
364      */
getNavBarPosition()365     public NavBarPosition getNavBarPosition() {
366         return mNavBarPosition;
367     }
368 
369     /**
370      * @return whether the current nav mode is fully gestural.
371      */
isFullyGesturalNavMode()372     public boolean isFullyGesturalNavMode() {
373         return mMode == NO_BUTTON;
374     }
375 
376     /**
377      * @return whether the current nav mode has some gestures (either 2 or 0 button mode).
378      */
isGesturalNavMode()379     public boolean isGesturalNavMode() {
380         return mMode == TWO_BUTTONS || mMode == NO_BUTTON;
381     }
382 
383     /**
384      * @return whether the current nav mode is button-based.
385      */
isButtonNavMode()386     public boolean isButtonNavMode() {
387         return mMode == THREE_BUTTONS;
388     }
389 
390     /**
391      * @return the display id for the display that Launcher is running on.
392      */
getDisplayId()393     public int getDisplayId() {
394         return mDisplayId;
395     }
396 
397     /**
398      * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
399      * will be called back immediately.
400      */
runOnUserUnlocked(Runnable action)401     public void runOnUserUnlocked(Runnable action) {
402         if (mIsUserUnlocked) {
403             action.run();
404         } else {
405             mUserUnlockedActions.add(action);
406         }
407     }
408 
409     /**
410      * @return whether the user is unlocked.
411      */
isUserUnlocked()412     public boolean isUserUnlocked() {
413         return mIsUserUnlocked;
414     }
415 
416     /**
417      * @return whether the user has completed setup wizard
418      */
isUserSetupComplete()419     public boolean isUserSetupComplete() {
420         return mIsUserSetupComplete;
421     }
422 
notifyUserUnlocked()423     private void notifyUserUnlocked() {
424         for (Runnable action : mUserUnlockedActions) {
425             action.run();
426         }
427         mUserUnlockedActions.clear();
428         Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
429     }
430 
431     /**
432      * @return whether the given running task info matches the gesture-blocked activity.
433      */
isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo)434     public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
435         return runningTaskInfo != null
436                 && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
437     }
438 
439     /**
440      * @return the packages of gesture-blocked activities.
441      */
getGestureBlockedActivityPackages()442     public List<String> getGestureBlockedActivityPackages() {
443         return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
444                 .collect(Collectors.toList());
445     }
446 
447     /**
448      * Updates the system ui state flags from SystemUI.
449      */
setSystemUiFlags(int stateFlags)450     public void setSystemUiFlags(int stateFlags) {
451         mSystemUiStateFlags = stateFlags;
452     }
453 
454     /**
455      * @return the system ui state flags.
456      */
457     // TODO(141886704): See if we can remove this
getSystemUiStateFlags()458     public @SystemUiStateFlags int getSystemUiStateFlags() {
459         return mSystemUiStateFlags;
460     }
461 
462     /**
463      * @return whether SystemUI is in a state where we can start a system gesture.
464      */
canStartSystemGesture()465     public boolean canStartSystemGesture() {
466         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
467                 || mTaskListFrozen;
468         return canStartWithNavHidden
469                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
470                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
471                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
472                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
473     }
474 
475     /**
476      * @return whether the keyguard is showing and is occluded by an app showing above the keyguard
477      *         (like camera or maps)
478      */
isKeyguardShowingOccluded()479     public boolean isKeyguardShowingOccluded() {
480         return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
481     }
482 
483     /**
484      * @return whether screen pinning is enabled and active
485      */
isScreenPinningActive()486     public boolean isScreenPinningActive() {
487         return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
488     }
489 
490     /**
491      * @return whether the bubble stack is expanded
492      */
isBubblesExpanded()493     public boolean isBubblesExpanded() {
494         return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
495     }
496 
497     /**
498      * @return whether the global actions dialog is showing
499      */
isGlobalActionsShowing()500     public boolean isGlobalActionsShowing() {
501         return (mSystemUiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0;
502     }
503 
504     /**
505      * @return whether lock-task mode is active
506      */
isLockToAppActive()507     public boolean isLockToAppActive() {
508         return ActivityManagerWrapper.getInstance().isLockToAppActive();
509     }
510 
511     /**
512      * @return whether the accessibility menu is available.
513      */
isAccessibilityMenuAvailable()514     public boolean isAccessibilityMenuAvailable() {
515         return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
516     }
517 
518     /**
519      * @return whether the accessibility menu shortcut is available.
520      */
isAccessibilityMenuShortcutAvailable()521     public boolean isAccessibilityMenuShortcutAvailable() {
522         return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
523     }
524 
525     /**
526      * @return whether home is disabled (either by SUW/SysUI/device policy)
527      */
isHomeDisabled()528     public boolean isHomeDisabled() {
529         return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
530     }
531 
532     /**
533      * @return whether overview is disabled (either by SUW/SysUI/device policy)
534      */
isOverviewDisabled()535     public boolean isOverviewDisabled() {
536         return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
537     }
538 
539     /**
540      * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
541      */
updateGestureTouchRegions()542     public void updateGestureTouchRegions() {
543         if (!mMode.hasGestures) {
544             return;
545         }
546 
547         mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
548     }
549 
550     /**
551      * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
552      */
isInSwipeUpTouchRegion(MotionEvent event)553     public boolean isInSwipeUpTouchRegion(MotionEvent event) {
554         return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
555     }
556 
557     /**
558      * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
559      *         is in the swipe up gesture region.
560      */
isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex)561     public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
562         return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
563                 event.getY(pointerIndex));
564     }
565 
566     /**
567      * Sets the region in screen space where the gestures should be deferred (ie. due to specific
568      * nav bar ui).
569      */
setDeferredGestureRegion(Region deferredGestureRegion)570     public void setDeferredGestureRegion(Region deferredGestureRegion) {
571         mDeferredGestureRegion.set(deferredGestureRegion);
572     }
573 
574     /**
575      * @return whether the given {@param event} is in the deferred gesture region indicating that
576      *         the Launcher should not immediately start the recents animation until the gesture
577      *         passes a certain threshold.
578      */
isInDeferredGestureRegion(MotionEvent event)579     public boolean isInDeferredGestureRegion(MotionEvent event) {
580         return mDeferredGestureRegion.contains((int) event.getX(), (int) event.getY());
581     }
582 
583     /**
584      * @return whether the given {@param event} is in the app-requested gesture-exclusion region.
585      *         This is only used for quickswitch, and not swipe up.
586      */
isInExclusionRegion(MotionEvent event)587     public boolean isInExclusionRegion(MotionEvent event) {
588         // mExclusionRegion can change on binder thread, use a local instance here.
589         Region exclusionRegion = mExclusionRegion;
590         return mMode == NO_BUTTON && exclusionRegion != null
591                 && exclusionRegion.contains((int) event.getX(), (int) event.getY());
592     }
593 
594     /**
595      * Sets whether the assistant is available.
596      */
setAssistantAvailable(boolean assistantAvailable)597     public void setAssistantAvailable(boolean assistantAvailable) {
598         mAssistantAvailable = assistantAvailable;
599     }
600 
601     /**
602      * Sets the visibility fraction of the assistant.
603      */
setAssistantVisibility(float visibility)604     public void setAssistantVisibility(float visibility) {
605         mAssistantVisibility = visibility;
606     }
607 
608     /**
609      * @return the visibility fraction of the assistant.
610      */
getAssistantVisibility()611     public float getAssistantVisibility() {
612         return mAssistantVisibility;
613     }
614 
615     /**
616      * @param ev An ACTION_DOWN motion event
617      * @param task Info for the currently running task
618      * @return whether the given motion event can trigger the assistant over the current task.
619      */
canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task)620     public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
621         return mAssistantAvailable
622                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
623                 && mOrientationTouchTransformer.touchInAssistantRegion(ev)
624                 && !isLockToAppActive()
625                 && !isGestureBlockedActivity(task);
626     }
627 
628     /**
629      * *May* apply a transform on the motion event if it lies in the nav bar region for another
630      * orientation that is currently being tracked as a part of quickstep
631      */
setOrientationTransformIfNeeded(MotionEvent event)632     void setOrientationTransformIfNeeded(MotionEvent event) {
633         // negative coordinates bug b/143901881
634         if (event.getX() < 0 || event.getY() < 0) {
635             event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
636         }
637         mOrientationTouchTransformer.transform(event);
638     }
639 
enableMultipleRegions(boolean enable)640     private void enableMultipleRegions(boolean enable) {
641         mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
642         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
643         if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
644             // Clear any previous state from sensor manager
645             mSensorRotation = mCurrentAppRotation;
646             mOrientationListener.enable();
647         } else {
648             mOrientationListener.disable();
649         }
650     }
651 
onStartGesture()652     public void onStartGesture() {
653         if (mTaskListFrozen) {
654             // Prioritize whatever nav bar user touches once in quickstep
655             // This case is specifically when user changes what nav bar they are using mid
656             // quickswitch session before tasks list is unfrozen
657             notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
658         }
659     }
660 
onEndTargetCalculated(GestureState.GestureEndTarget endTarget, BaseActivityInterface activityInterface)661     void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
662             BaseActivityInterface activityInterface) {
663         if (endTarget == GestureState.GestureEndTarget.RECENTS) {
664             mInOverview = true;
665             if (!mTaskListFrozen) {
666                 // If we're in landscape w/o ever quickswitching, show the navbar in landscape
667                 enableMultipleRegions(true);
668             }
669             activityInterface.onExitOverview(this, mExitOverviewRunnable);
670         } else if (endTarget == GestureState.GestureEndTarget.HOME) {
671             enableMultipleRegions(false);
672         } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
673             if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
674                 // First gesture to start quickswitch
675                 enableMultipleRegions(true);
676             } else {
677                 notifySysuiOfCurrentRotation(
678                         mOrientationTouchTransformer.getCurrentActiveRotation());
679             }
680 
681             // A new gesture is starting, reset the current device rotation
682             // This is done under the assumption that the user won't rotate the phone and then
683             // quickswitch in the old orientation.
684             mPrioritizeDeviceRotation = false;
685         } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
686             if (!mTaskListFrozen) {
687                 // touched nav bar but didn't go anywhere and not quickswitching, do nothing
688                 return;
689             }
690             notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
691         }
692     }
693 
notifySysuiOfCurrentRotation(int rotation)694     private void notifySysuiOfCurrentRotation(int rotation) {
695         UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
696                 .onQuickSwitchToNewTask(rotation));
697     }
698 
699     /**
700      * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
701      * notifies system UI of the primary rotation the user is interacting with
702      */
toggleSecondaryNavBarsForRotation()703     private void toggleSecondaryNavBarsForRotation() {
704         mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
705         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
706     }
707 
getCurrentActiveRotation()708     public int getCurrentActiveRotation() {
709         if (!mMode.hasGestures) {
710             // touch rotation should always match that of display for 3 button
711             return mDisplayRotation;
712         }
713         return mOrientationTouchTransformer.getCurrentActiveRotation();
714     }
715 
getDisplayRotation()716     public int getDisplayRotation() {
717         return mDisplayRotation;
718     }
719 
dump(PrintWriter pw)720     public void dump(PrintWriter pw) {
721         pw.println("DeviceState:");
722         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
723         pw.println("  systemUiFlags=" + mSystemUiStateFlags);
724         pw.println("  systemUiFlagsDesc="
725                 + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
726         pw.println("  assistantAvailable=" + mAssistantAvailable);
727         pw.println("  assistantDisabled="
728                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
729         pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
730         pw.println("  displayRotation=" + getDisplayRotation());
731         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
732         mOrientationTouchTransformer.dump(pw);
733     }
734 }
735