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 20 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL; 21 import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY; 22 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; 23 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; 24 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; 25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; 27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; 28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; 29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; 30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; 31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; 32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 36 37 import android.app.ActivityManager; 38 import android.content.BroadcastReceiver; 39 import android.content.ComponentName; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.IntentFilter; 43 import android.content.res.Resources; 44 import android.graphics.Region; 45 import android.os.Process; 46 import android.os.UserManager; 47 import android.provider.Settings; 48 import android.text.TextUtils; 49 import android.view.MotionEvent; 50 51 import androidx.annotation.BinderThread; 52 53 import com.android.launcher3.R; 54 import com.android.launcher3.Utilities; 55 import com.android.launcher3.util.DefaultDisplay; 56 import com.android.launcher3.util.SecureSettingsObserver; 57 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; 58 import com.android.quickstep.util.NavBarPosition; 59 import com.android.systemui.shared.system.ActivityManagerWrapper; 60 import com.android.systemui.shared.system.QuickStepContract; 61 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; 62 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat; 63 64 import java.io.PrintWriter; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.stream.Collectors; 68 69 /** 70 * Manages the state of the system during a swipe up gesture. 71 */ 72 public class RecentsAnimationDeviceState implements 73 NavigationModeChangeListener, 74 DefaultDisplay.DisplayInfoChangeListener { 75 76 private final Context mContext; 77 private final SysUINavigationMode mSysUiNavMode; 78 private final DefaultDisplay mDefaultDisplay; 79 private final int mDisplayId; 80 private final RotationTouchHelper mRotationTouchHelper; 81 82 private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>(); 83 84 private @SystemUiStateFlags int mSystemUiStateFlags; 85 private SysUINavigationMode.Mode mMode = THREE_BUTTONS; 86 private NavBarPosition mNavBarPosition; 87 88 private final Region mDeferredGestureRegion = new Region(); 89 private boolean mAssistantAvailable; 90 private float mAssistantVisibility; 91 92 private boolean mIsUserUnlocked; 93 private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>(); 94 private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() { 95 @Override 96 public void onReceive(Context context, Intent intent) { 97 if (ACTION_USER_UNLOCKED.equals(intent.getAction())) { 98 mIsUserUnlocked = true; 99 notifyUserUnlocked(); 100 } 101 } 102 }; 103 104 private Region mExclusionRegion; 105 private SystemGestureExclusionListenerCompat mExclusionListener; 106 107 private final List<ComponentName> mGestureBlockedActivities; 108 109 private boolean mIsUserSetupComplete; 110 RecentsAnimationDeviceState(Context context)111 public RecentsAnimationDeviceState(Context context) { 112 mContext = context; 113 mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context); 114 mDefaultDisplay = DefaultDisplay.INSTANCE.get(context); 115 mDisplayId = mDefaultDisplay.getInfo().id; 116 runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this)); 117 mRotationTouchHelper = new RotationTouchHelper(context); 118 runOnDestroy(mRotationTouchHelper::destroy); 119 120 // Register for user unlocked if necessary 121 mIsUserUnlocked = context.getSystemService(UserManager.class) 122 .isUserUnlocked(Process.myUserHandle()); 123 if (!mIsUserUnlocked) { 124 mContext.registerReceiver(mUserUnlockedReceiver, 125 new IntentFilter(ACTION_USER_UNLOCKED)); 126 } 127 runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver)); 128 129 // Register for exclusion updates 130 mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) { 131 @Override 132 @BinderThread 133 public void onExclusionChanged(Region region) { 134 // Assignments are atomic, it should be safe on binder thread 135 mExclusionRegion = region; 136 } 137 }; 138 runOnDestroy(mExclusionListener::unregister); 139 140 // Register for navigation mode changes 141 onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this)); 142 runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this)); 143 144 // Add any blocked activities 145 String[] blockingActivities; 146 try { 147 blockingActivities = 148 context.getResources().getStringArray(R.array.gesture_blocking_activities); 149 } catch (Resources.NotFoundException e) { 150 blockingActivities = new String[0]; 151 } 152 mGestureBlockedActivities = new ArrayList<>(blockingActivities.length); 153 for (String blockingActivity : blockingActivities) { 154 if (!TextUtils.isEmpty(blockingActivity)) { 155 mGestureBlockedActivities.add( 156 ComponentName.unflattenFromString(blockingActivity)); 157 } 158 } 159 160 SecureSettingsObserver userSetupObserver = new SecureSettingsObserver( 161 context.getContentResolver(), 162 e -> mIsUserSetupComplete = e, 163 Settings.Secure.USER_SETUP_COMPLETE, 164 0); 165 mIsUserSetupComplete = userSetupObserver.getValue(); 166 if (!mIsUserSetupComplete) { 167 userSetupObserver.register(); 168 runOnDestroy(userSetupObserver::unregister); 169 } 170 } 171 runOnDestroy(Runnable action)172 private void runOnDestroy(Runnable action) { 173 mOnDestroyActions.add(action); 174 } 175 176 /** 177 * Cleans up all the registered listeners and receivers. 178 */ destroy()179 public void destroy() { 180 for (Runnable r : mOnDestroyActions) { 181 r.run(); 182 } 183 } 184 185 /** 186 * Adds a listener for the nav mode change, guaranteed to be called after the device state's 187 * mode has changed. 188 */ addNavigationModeChangedCallback(NavigationModeChangeListener listener)189 public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) { 190 listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener)); 191 runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener)); 192 } 193 194 @Override onNavigationModeChanged(SysUINavigationMode.Mode newMode)195 public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) { 196 mDefaultDisplay.removeChangeListener(this); 197 mDefaultDisplay.addChangeListener(this); 198 onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL); 199 200 if (newMode == NO_BUTTON) { 201 mExclusionListener.register(); 202 } else { 203 mExclusionListener.unregister(); 204 } 205 206 mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo()); 207 208 mMode = newMode; 209 } 210 211 @Override onDisplayInfoChanged(DefaultDisplay.Info info, int flags)212 public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { 213 if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) { 214 // ignore displays that aren't running launcher and frame refresh rate changes 215 return; 216 } 217 218 if (!mMode.hasGestures) { 219 return; 220 } 221 mNavBarPosition = new NavBarPosition(mMode, info); 222 } 223 224 /** 225 * @return the current navigation mode for the device. 226 */ getNavMode()227 public SysUINavigationMode.Mode getNavMode() { 228 return mMode; 229 } 230 231 /** 232 * @return the nav bar position for the current nav bar mode and display rotation. 233 */ getNavBarPosition()234 public NavBarPosition getNavBarPosition() { 235 return mNavBarPosition; 236 } 237 238 /** 239 * @return whether the current nav mode is fully gestural. 240 */ isFullyGesturalNavMode()241 public boolean isFullyGesturalNavMode() { 242 return mMode == NO_BUTTON; 243 } 244 245 /** 246 * @return whether the current nav mode has some gestures (either 2 or 0 button mode). 247 */ isGesturalNavMode()248 public boolean isGesturalNavMode() { 249 return mMode == TWO_BUTTONS || mMode == NO_BUTTON; 250 } 251 252 /** 253 * @return whether the current nav mode is button-based. 254 */ isButtonNavMode()255 public boolean isButtonNavMode() { 256 return mMode == THREE_BUTTONS; 257 } 258 259 /** 260 * @return the display id for the display that Launcher is running on. 261 */ getDisplayId()262 public int getDisplayId() { 263 return mDisplayId; 264 } 265 266 /** 267 * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener 268 * will be called back immediately. 269 */ runOnUserUnlocked(Runnable action)270 public void runOnUserUnlocked(Runnable action) { 271 if (mIsUserUnlocked) { 272 action.run(); 273 } else { 274 mUserUnlockedActions.add(action); 275 } 276 } 277 278 /** 279 * @return whether the user is unlocked. 280 */ isUserUnlocked()281 public boolean isUserUnlocked() { 282 return mIsUserUnlocked; 283 } 284 285 /** 286 * @return whether the user has completed setup wizard 287 */ isUserSetupComplete()288 public boolean isUserSetupComplete() { 289 return mIsUserSetupComplete; 290 } 291 notifyUserUnlocked()292 private void notifyUserUnlocked() { 293 for (Runnable action : mUserUnlockedActions) { 294 action.run(); 295 } 296 mUserUnlockedActions.clear(); 297 Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver); 298 } 299 300 /** 301 * @return whether the given running task info matches the gesture-blocked activity. 302 */ isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo)303 public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) { 304 return runningTaskInfo != null 305 && mGestureBlockedActivities.contains(runningTaskInfo.topActivity); 306 } 307 308 /** 309 * @return the packages of gesture-blocked activities. 310 */ getGestureBlockedActivityPackages()311 public List<String> getGestureBlockedActivityPackages() { 312 return mGestureBlockedActivities.stream().map(ComponentName::getPackageName) 313 .collect(Collectors.toList()); 314 } 315 316 /** 317 * Updates the system ui state flags from SystemUI. 318 */ setSystemUiFlags(int stateFlags)319 public void setSystemUiFlags(int stateFlags) { 320 mSystemUiStateFlags = stateFlags; 321 } 322 323 /** 324 * @return the system ui state flags. 325 */ 326 // TODO(141886704): See if we can remove this getSystemUiStateFlags()327 public @SystemUiStateFlags int getSystemUiStateFlags() { 328 return mSystemUiStateFlags; 329 } 330 331 /** 332 * @return whether SystemUI is in a state where we can start a system gesture. 333 */ canStartSystemGesture()334 public boolean canStartSystemGesture() { 335 boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 336 || mRotationTouchHelper.isTaskListFrozen(); 337 return canStartWithNavHidden 338 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0 339 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0 340 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 341 || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0); 342 } 343 344 /** 345 * @return whether the keyguard is showing and is occluded by an app showing above the keyguard 346 * (like camera or maps) 347 */ isKeyguardShowingOccluded()348 public boolean isKeyguardShowingOccluded() { 349 return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0; 350 } 351 352 /** 353 * @return whether screen pinning is enabled and active 354 */ isScreenPinningActive()355 public boolean isScreenPinningActive() { 356 return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0; 357 } 358 359 /** 360 * @return whether the bubble stack is expanded 361 */ isBubblesExpanded()362 public boolean isBubblesExpanded() { 363 return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0; 364 } 365 366 /** 367 * @return whether the global actions dialog is showing 368 */ isGlobalActionsShowing()369 public boolean isGlobalActionsShowing() { 370 return (mSystemUiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0; 371 } 372 373 /** 374 * @return whether lock-task mode is active 375 */ isLockToAppActive()376 public boolean isLockToAppActive() { 377 return ActivityManagerWrapper.getInstance().isLockToAppActive(); 378 } 379 380 /** 381 * @return whether the accessibility menu is available. 382 */ isAccessibilityMenuAvailable()383 public boolean isAccessibilityMenuAvailable() { 384 return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; 385 } 386 387 /** 388 * @return whether the accessibility menu shortcut is available. 389 */ isAccessibilityMenuShortcutAvailable()390 public boolean isAccessibilityMenuShortcutAvailable() { 391 return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; 392 } 393 394 /** 395 * @return whether home is disabled (either by SUW/SysUI/device policy) 396 */ isHomeDisabled()397 public boolean isHomeDisabled() { 398 return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0; 399 } 400 401 /** 402 * @return whether overview is disabled (either by SUW/SysUI/device policy) 403 */ isOverviewDisabled()404 public boolean isOverviewDisabled() { 405 return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0; 406 } 407 408 /** 409 * Sets the region in screen space where the gestures should be deferred (ie. due to specific 410 * nav bar ui). 411 */ setDeferredGestureRegion(Region deferredGestureRegion)412 public void setDeferredGestureRegion(Region deferredGestureRegion) { 413 mDeferredGestureRegion.set(deferredGestureRegion); 414 } 415 416 /** 417 * @return whether the given {@param event} is in the deferred gesture region indicating that 418 * the Launcher should not immediately start the recents animation until the gesture 419 * passes a certain threshold. 420 */ isInDeferredGestureRegion(MotionEvent event)421 public boolean isInDeferredGestureRegion(MotionEvent event) { 422 return mDeferredGestureRegion.contains((int) event.getX(), (int) event.getY()); 423 } 424 425 /** 426 * @return whether the given {@param event} is in the app-requested gesture-exclusion region. 427 * This is only used for quickswitch, and not swipe up. 428 */ isInExclusionRegion(MotionEvent event)429 public boolean isInExclusionRegion(MotionEvent event) { 430 // mExclusionRegion can change on binder thread, use a local instance here. 431 Region exclusionRegion = mExclusionRegion; 432 return mMode == NO_BUTTON && exclusionRegion != null 433 && exclusionRegion.contains((int) event.getX(), (int) event.getY()); 434 } 435 436 /** 437 * Sets whether the assistant is available. 438 */ setAssistantAvailable(boolean assistantAvailable)439 public void setAssistantAvailable(boolean assistantAvailable) { 440 mAssistantAvailable = assistantAvailable; 441 } 442 443 /** 444 * Sets the visibility fraction of the assistant. 445 */ setAssistantVisibility(float visibility)446 public void setAssistantVisibility(float visibility) { 447 mAssistantVisibility = visibility; 448 } 449 450 /** 451 * @return the visibility fraction of the assistant. 452 */ getAssistantVisibility()453 public float getAssistantVisibility() { 454 return mAssistantVisibility; 455 } 456 457 /** 458 * @param ev An ACTION_DOWN motion event 459 * @param task Info for the currently running task 460 * @return whether the given motion event can trigger the assistant over the current task. 461 */ canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task)462 public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) { 463 return mAssistantAvailable 464 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) 465 && mRotationTouchHelper.touchInAssistantRegion(ev) 466 && !isLockToAppActive() 467 && !isGestureBlockedActivity(task); 468 } 469 getRotationTouchHelper()470 public RotationTouchHelper getRotationTouchHelper() { 471 return mRotationTouchHelper; 472 } 473 dump(PrintWriter pw)474 public void dump(PrintWriter pw) { 475 pw.println("DeviceState:"); 476 pw.println(" canStartSystemGesture=" + canStartSystemGesture()); 477 pw.println(" systemUiFlags=" + mSystemUiStateFlags); 478 pw.println(" systemUiFlagsDesc=" 479 + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags)); 480 pw.println(" assistantAvailable=" + mAssistantAvailable); 481 pw.println(" assistantDisabled=" 482 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)); 483 pw.println(" isUserUnlocked=" + mIsUserUnlocked); 484 mRotationTouchHelper.dump(pw); 485 } 486 } 487