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