NavigationBarFragment.java revision 1e28a5ae5031d1407cd2b55d5140ce336428a38b
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15package com.android.systemui.statusbar.phone; 16 17import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; 18import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; 19import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; 20import static android.app.StatusBarManager.windowStateToString; 21 22import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; 23import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; 24import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE; 25import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions; 26import static com.android.systemui.OverviewProxyService.OverviewProxyListener; 27 28import android.accessibilityservice.AccessibilityServiceInfo; 29import android.animation.Animator; 30import android.animation.AnimatorListenerAdapter; 31import android.animation.ObjectAnimator; 32import android.annotation.IdRes; 33import android.annotation.Nullable; 34import android.app.ActivityManager; 35import android.app.ActivityManagerNative; 36import android.app.Fragment; 37import android.app.IActivityManager; 38import android.app.StatusBarManager; 39import android.content.BroadcastReceiver; 40import android.content.ContentResolver; 41import android.content.Context; 42import android.content.Intent; 43import android.content.IntentFilter; 44import android.content.res.Configuration; 45import android.database.ContentObserver; 46import android.graphics.PixelFormat; 47import android.graphics.Rect; 48import android.graphics.drawable.AnimatedVectorDrawable; 49import android.graphics.drawable.Drawable; 50import android.inputmethodservice.InputMethodService; 51import android.os.Binder; 52import android.os.Bundle; 53import android.os.Handler; 54import android.os.IBinder; 55import android.os.Message; 56import android.os.RemoteException; 57import android.os.UserHandle; 58import android.provider.Settings; 59import android.support.annotation.VisibleForTesting; 60import android.telecom.TelecomManager; 61import android.text.TextUtils; 62import android.util.Log; 63import android.view.IRotationWatcher.Stub; 64import android.view.KeyEvent; 65import android.view.LayoutInflater; 66import android.view.MotionEvent; 67import android.view.Surface; 68import android.view.View; 69import android.view.ViewGroup; 70import android.view.WindowManager; 71import android.view.WindowManager.LayoutParams; 72import android.view.WindowManagerGlobal; 73import android.view.accessibility.AccessibilityEvent; 74import android.view.accessibility.AccessibilityManager; 75import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; 76 77import com.android.internal.logging.MetricsLogger; 78import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 79import com.android.internal.util.LatencyTracker; 80import com.android.systemui.Dependency; 81import com.android.systemui.Interpolators; 82import com.android.systemui.OverviewProxyService; 83import com.android.systemui.R; 84import com.android.systemui.SysUiServiceProvider; 85import com.android.systemui.assist.AssistManager; 86import com.android.systemui.fragments.FragmentHostManager; 87import com.android.systemui.fragments.FragmentHostManager.FragmentListener; 88import com.android.systemui.recents.Recents; 89import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; 90import com.android.systemui.shared.system.ActivityManagerWrapper; 91import com.android.systemui.stackdivider.Divider; 92import com.android.systemui.statusbar.CommandQueue; 93import com.android.systemui.statusbar.CommandQueue.Callbacks; 94import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 95import com.android.systemui.statusbar.policy.KeyButtonDrawable; 96import com.android.systemui.statusbar.policy.KeyButtonView; 97import com.android.systemui.statusbar.policy.RotationLockController; 98import com.android.systemui.statusbar.stack.StackStateAnimator; 99 100import java.io.FileDescriptor; 101import java.io.PrintWriter; 102import java.util.List; 103import java.util.Locale; 104 105/** 106 * Fragment containing the NavigationBarFragment. Contains logic for what happens 107 * on clicks and view states of the nav bar. 108 */ 109public class NavigationBarFragment extends Fragment implements Callbacks { 110 111 public static final String TAG = "NavigationBar"; 112 private static final boolean DEBUG = false; 113 private static final String EXTRA_DISABLE_STATE = "disabled_state"; 114 115 private final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100; 116 private final static int ROTATE_BUTTON_LOOP_DURATION_MS = 2000; 117 118 /** Allow some time inbetween the long press for back and recents. */ 119 private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; 120 121 protected NavigationBarView mNavigationBarView = null; 122 protected AssistManager mAssistManager; 123 124 private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; 125 126 private int mNavigationIconHints = 0; 127 private int mNavigationBarMode; 128 private boolean mAccessibilityFeedbackEnabled; 129 private AccessibilityManager mAccessibilityManager; 130 private MagnificationContentObserver mMagnificationObserver; 131 private ContentResolver mContentResolver; 132 private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); 133 134 private int mDisabledFlags1; 135 private StatusBar mStatusBar; 136 private Recents mRecents; 137 private Divider mDivider; 138 private WindowManager mWindowManager; 139 private CommandQueue mCommandQueue; 140 private long mLastLockToAppLongPress; 141 142 private Locale mLocale; 143 private int mLayoutDirection; 144 145 private int mSystemUiVisibility; 146 private LightBarController mLightBarController; 147 148 private OverviewProxyService mOverviewProxyService; 149 150 public boolean mHomeBlockedThisTouch; 151 152 private int mLastRotationSuggestion; 153 private boolean mHoveringRotationSuggestion; 154 private RotationLockController mRotationLockController; 155 private TaskStackListenerImpl mTaskStackListener; 156 157 private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false); 158 private Animator mRotateHideAnimator; 159 160 private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { 161 @Override 162 public void onConnectionChanged(boolean isConnected) { 163 mNavigationBarView.onOverviewProxyConnectionChanged(isConnected); 164 updateScreenPinningGestures(); 165 } 166 167 @Override 168 public void onRecentsAnimationStarted() { 169 mNavigationBarView.setRecentsAnimationStarted(true); 170 } 171 172 @Override 173 public void onInteractionFlagsChanged(@InteractionType int flags) { 174 mNavigationBarView.updateStates(); 175 } 176 }; 177 178 // ----- Fragment Lifecycle Callbacks ----- 179 180 @Override 181 public void onCreate(@Nullable Bundle savedInstanceState) { 182 super.onCreate(savedInstanceState); 183 mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class); 184 mCommandQueue.addCallbacks(this); 185 mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class); 186 mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class); 187 mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class); 188 mWindowManager = getContext().getSystemService(WindowManager.class); 189 mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); 190 Dependency.get(AccessibilityManagerWrapper.class).addCallback( 191 mAccessibilityListener); 192 mContentResolver = getContext().getContentResolver(); 193 mMagnificationObserver = new MagnificationContentObserver( 194 getContext().getMainThreadHandler()); 195 mContentResolver.registerContentObserver(Settings.Secure.getUriFor( 196 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false, 197 mMagnificationObserver, UserHandle.USER_ALL); 198 199 if (savedInstanceState != null) { 200 mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); 201 } 202 mAssistManager = Dependency.get(AssistManager.class); 203 mOverviewProxyService = Dependency.get(OverviewProxyService.class); 204 205 try { 206 WindowManagerGlobal.getWindowManagerService() 207 .watchRotation(mRotationWatcher, getContext().getDisplay().getDisplayId()); 208 } catch (RemoteException e) { 209 throw e.rethrowFromSystemServer(); 210 } 211 212 mRotationLockController = Dependency.get(RotationLockController.class); 213 214 // Register the task stack listener 215 mTaskStackListener = new TaskStackListenerImpl(); 216 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 217 } 218 219 @Override 220 public void onDestroy() { 221 super.onDestroy(); 222 mCommandQueue.removeCallbacks(this); 223 Dependency.get(AccessibilityManagerWrapper.class).removeCallback( 224 mAccessibilityListener); 225 mContentResolver.unregisterContentObserver(mMagnificationObserver); 226 try { 227 WindowManagerGlobal.getWindowManagerService() 228 .removeRotationWatcher(mRotationWatcher); 229 } catch (RemoteException e) { 230 throw e.rethrowFromSystemServer(); 231 } 232 233 // Unregister the task stack listener 234 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); 235 } 236 237 @Override 238 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 239 Bundle savedInstanceState) { 240 return inflater.inflate(R.layout.navigation_bar, container, false); 241 } 242 243 @Override 244 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 245 super.onViewCreated(view, savedInstanceState); 246 mNavigationBarView = (NavigationBarView) view; 247 248 mNavigationBarView.setDisabledFlags(mDisabledFlags1); 249 mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel()); 250 mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); 251 mNavigationBarView.setOnTouchListener(this::onNavigationTouch); 252 if (savedInstanceState != null) { 253 mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState); 254 } 255 256 prepareNavigationBarView(); 257 checkNavBarModes(); 258 259 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 260 filter.addAction(Intent.ACTION_SCREEN_ON); 261 getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); 262 notifyNavigationBarScreenOn(); 263 mOverviewProxyService.addCallback(mOverviewProxyListener); 264 } 265 266 @Override 267 public void onDestroyView() { 268 super.onDestroyView(); 269 mNavigationBarView.getLightTransitionsController().destroy(getContext()); 270 mOverviewProxyService.removeCallback(mOverviewProxyListener); 271 getContext().unregisterReceiver(mBroadcastReceiver); 272 } 273 274 @Override 275 public void onSaveInstanceState(Bundle outState) { 276 super.onSaveInstanceState(outState); 277 outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1); 278 if (mNavigationBarView != null) { 279 mNavigationBarView.getLightTransitionsController().saveState(outState); 280 } 281 } 282 283 @Override 284 public void onConfigurationChanged(Configuration newConfig) { 285 super.onConfigurationChanged(newConfig); 286 final Locale locale = getContext().getResources().getConfiguration().locale; 287 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 288 if (!locale.equals(mLocale) || ld != mLayoutDirection) { 289 if (DEBUG) { 290 Log.v(TAG, String.format( 291 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 292 locale, ld)); 293 } 294 mLocale = locale; 295 mLayoutDirection = ld; 296 refreshLayout(ld); 297 } 298 repositionNavigationBar(); 299 } 300 301 @Override 302 public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { 303 if (mNavigationBarView != null) { 304 pw.print(" mNavigationBarWindowState="); 305 pw.println(windowStateToString(mNavigationBarWindowState)); 306 pw.print(" mNavigationBarMode="); 307 pw.println(BarTransitions.modeToString(mNavigationBarMode)); 308 dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); 309 } 310 311 pw.print(" mNavigationBarView="); 312 if (mNavigationBarView == null) { 313 pw.println("null"); 314 } else { 315 mNavigationBarView.dump(fd, pw, args); 316 } 317 } 318 319 // ----- CommandQueue Callbacks ----- 320 321 @Override 322 public void setImeWindowStatus(IBinder token, int vis, int backDisposition, 323 boolean showImeSwitcher) { 324 boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; 325 int hints = mNavigationIconHints; 326 if (imeShown && backDisposition != InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS) { 327 hints |= NAVIGATION_HINT_BACK_ALT; 328 } else { 329 hints &= ~NAVIGATION_HINT_BACK_ALT; 330 } 331 if (showImeSwitcher) { 332 hints |= NAVIGATION_HINT_IME_SHOWN; 333 } else { 334 hints &= ~NAVIGATION_HINT_IME_SHOWN; 335 } 336 if (hints == mNavigationIconHints) return; 337 338 mNavigationIconHints = hints; 339 340 if (mNavigationBarView != null) { 341 mNavigationBarView.setNavigationIconHints(hints); 342 } 343 mStatusBar.checkBarModes(); 344 } 345 346 @Override 347 public void topAppWindowChanged(boolean showMenu) { 348 if (mNavigationBarView != null) { 349 mNavigationBarView.setMenuVisibility(showMenu); 350 } 351 } 352 353 @Override 354 public void setWindowState(int window, int state) { 355 if (mNavigationBarView != null 356 && window == StatusBarManager.WINDOW_NAVIGATION_BAR 357 && mNavigationBarWindowState != state) { 358 mNavigationBarWindowState = state; 359 if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state)); 360 } 361 } 362 363 @Override 364 public void onRotationProposal(final int rotation, boolean isValid) { 365 // This method will be called on rotation suggestion changes even if the proposed rotation 366 // is not valid for the top app. Use invalid rotation choices as a signal to remove the 367 // rotate button if shown. 368 369 if (!isValid) { 370 setRotateSuggestionButtonState(false); 371 return; 372 } 373 374 final int winRotation = mWindowManager.getDefaultDisplay().getRotation(); 375 if (rotation == winRotation) { 376 // Use this as a signal to remove any current suggestions 377 getView().getHandler().removeCallbacks(mRemoveRotationProposal); 378 setRotateSuggestionButtonState(false); 379 } else { 380 mLastRotationSuggestion = rotation; // Remember rotation for click 381 382 // Update the icon style to change animation parameters 383 if (mNavigationBarView != null) { 384 final boolean rotationCCW = isRotationAnimationCCW(winRotation, rotation); 385 int style; 386 if (winRotation == Surface.ROTATION_0 || winRotation == Surface.ROTATION_180) { 387 style = rotationCCW ? R.style.RotateButtonCCWStart90 : 388 R.style.RotateButtonCWStart90; 389 } else { // 90 or 270 390 style = rotationCCW ? R.style.RotateButtonCCWStart0 : 391 R.style.RotateButtonCWStart0; 392 } 393 mNavigationBarView.updateRotateSuggestionButtonStyle(style, true); 394 } 395 396 setRotateSuggestionButtonState(true); 397 rescheduleRotationTimeout(false); 398 mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN); 399 } 400 } 401 402 private boolean isRotationAnimationCCW(int from, int to) { 403 // All 180deg WM rotation animations are CCW, match that 404 if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false; 405 if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW 406 if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true; 407 if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true; 408 if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false; 409 if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW 410 if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW 411 if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true; 412 if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false; 413 if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false; 414 if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW 415 if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true; 416 return false; // Default 417 } 418 419 public void setRotateSuggestionButtonState(final boolean visible) { 420 setRotateSuggestionButtonState(visible, false); 421 } 422 423 public void setRotateSuggestionButtonState(final boolean visible, final boolean force) { 424 if (mNavigationBarView == null) return; 425 426 // At any point the the button can become invisible because an a11y service became active. 427 // Similarly, a call to make the button visible may be rejected because an a11y service is 428 // active. Must account for this. 429 430 ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton(); 431 final boolean currentlyVisible = mNavigationBarView.isRotateButtonVisible(); 432 433 // Rerun a show animation to indicate change but don't rerun a hide animation 434 if (!visible && !currentlyVisible) return; 435 436 View view = rotBtn.getCurrentView(); 437 if (view == null) return; 438 439 KeyButtonDrawable kbd = rotBtn.getImageDrawable(); 440 if (kbd == null) return; 441 442 // The KBD and AVD is recreated every new valid suggestion because of style changes. 443 AnimatedVectorDrawable animIcon = null; 444 if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) { 445 animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0); 446 } 447 448 if (visible) { // Appear and change (cannot force) 449 // Stop any currently running hide animations 450 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { 451 mRotateHideAnimator.pause(); 452 } 453 454 // Reset the alpha if any has changed due to hide animation 455 view.setAlpha(1f); 456 457 // Run the rotate icon's animation if it has one 458 if (animIcon != null) { 459 animIcon.reset(); 460 animIcon.start(); 461 } 462 463 // Set visibility, may fail if a11y service is active. 464 // If invisible, call will stop animation. 465 mNavigationBarView.setRotateButtonVisibility(true); 466 467 } else { // Hide 468 469 if (force) { 470 // If a hide animator is running stop it and make invisible 471 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { 472 mRotateHideAnimator.pause(); 473 } 474 mNavigationBarView.setRotateButtonVisibility(false); 475 return; 476 } 477 478 // Don't start any new hide animations if one is running 479 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; 480 481 ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 482 0f); 483 fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); 484 fadeOut.setInterpolator(Interpolators.LINEAR); 485 fadeOut.addListener(new AnimatorListenerAdapter() { 486 @Override 487 public void onAnimationEnd(Animator animation) { 488 mNavigationBarView.setRotateButtonVisibility(false); 489 } 490 }); 491 492 mRotateHideAnimator = fadeOut; 493 fadeOut.start(); 494 } 495 } 496 497 private void rescheduleRotationTimeout(final boolean reasonHover) { 498 // May be called due to a new rotation proposal or a change in hover state 499 if (reasonHover) { 500 // Don't reschedule if a hide animator is running 501 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; 502 // Don't reschedule if not visible 503 if (!mNavigationBarView.isRotateButtonVisible()) return; 504 } 505 506 Handler h = getView().getHandler(); 507 h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal 508 h.postDelayed(mRemoveRotationProposal, 509 computeRotationProposalTimeout()); // Schedule timeout 510 } 511 512 private int computeRotationProposalTimeout() { 513 if (mAccessibilityFeedbackEnabled) return 20000; 514 if (mHoveringRotationSuggestion) return 16000; 515 return 6000; 516 } 517 518 // Injected from StatusBar at creation. 519 public void setCurrentSysuiVisibility(int systemUiVisibility) { 520 mSystemUiVisibility = systemUiVisibility; 521 mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility, 522 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT, 523 View.NAVIGATION_BAR_TRANSPARENT); 524 checkNavBarModes(); 525 mStatusBar.touchAutoHide(); 526 mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */, 527 true /* nbModeChanged */, mNavigationBarMode); 528 } 529 530 @Override 531 public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, 532 int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { 533 final int oldVal = mSystemUiVisibility; 534 final int newVal = (oldVal & ~mask) | (vis & mask); 535 final int diff = newVal ^ oldVal; 536 boolean nbModeChanged = false; 537 if (diff != 0) { 538 mSystemUiVisibility = newVal; 539 540 // update navigation bar mode 541 final int nbMode = getView() == null 542 ? -1 : mStatusBar.computeBarMode(oldVal, newVal, 543 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT, 544 View.NAVIGATION_BAR_TRANSPARENT); 545 nbModeChanged = nbMode != -1; 546 if (nbModeChanged) { 547 if (mNavigationBarMode != nbMode) { 548 mNavigationBarMode = nbMode; 549 checkNavBarModes(); 550 } 551 mStatusBar.touchAutoHide(); 552 } 553 } 554 555 mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged, 556 mNavigationBarMode); 557 } 558 559 @Override 560 public void disable(int state1, int state2, boolean animate) { 561 // All navigation bar flags are in state1. 562 int masked = state1 & (StatusBarManager.DISABLE_HOME 563 | StatusBarManager.DISABLE_RECENT 564 | StatusBarManager.DISABLE_BACK 565 | StatusBarManager.DISABLE_SEARCH); 566 if (masked != mDisabledFlags1) { 567 mDisabledFlags1 = masked; 568 if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1); 569 updateScreenPinningGestures(); 570 } 571 } 572 573 // ----- Internal stuffz ----- 574 575 private void refreshLayout(int layoutDirection) { 576 if (mNavigationBarView != null) { 577 mNavigationBarView.setLayoutDirection(layoutDirection); 578 } 579 } 580 581 private boolean shouldDisableNavbarGestures() { 582 return !mStatusBar.isDeviceProvisioned() 583 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0 584 || mNavigationBarView.getRecentsButton().getVisibility() != View.VISIBLE; 585 } 586 587 private void repositionNavigationBar() { 588 if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; 589 590 prepareNavigationBarView(); 591 592 mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(), 593 ((View) mNavigationBarView.getParent()).getLayoutParams()); 594 } 595 596 private void updateScreenPinningGestures() { 597 if (mNavigationBarView == null) { 598 return; 599 } 600 601 // Change the cancel pin gesture to home and back if recents button is invisible 602 boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible(); 603 ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); 604 ButtonDispatcher backButton = mNavigationBarView.getBackButton(); 605 if (recentsVisible) { 606 homeButton.setOnLongClickListener(this::onHomeLongClick); 607 backButton.setOnLongClickListener(this::onLongPressBackRecents); 608 } else { 609 homeButton.setOnLongClickListener(this::onLongPressBackHome); 610 backButton.setOnLongClickListener(this::onLongPressBackHome); 611 } 612 } 613 614 private void notifyNavigationBarScreenOn() { 615 mNavigationBarView.notifyScreenOn(); 616 } 617 618 private void prepareNavigationBarView() { 619 mNavigationBarView.reorient(); 620 621 ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); 622 recentsButton.setOnClickListener(this::onRecentsClick); 623 recentsButton.setOnTouchListener(this::onRecentsTouch); 624 recentsButton.setLongClickable(true); 625 recentsButton.setOnLongClickListener(this::onLongPressBackRecents); 626 627 ButtonDispatcher backButton = mNavigationBarView.getBackButton(); 628 backButton.setLongClickable(true); 629 630 ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); 631 homeButton.setOnTouchListener(this::onHomeTouch); 632 633 ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); 634 accessibilityButton.setOnClickListener(this::onAccessibilityClick); 635 accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); 636 updateAccessibilityServicesState(mAccessibilityManager); 637 638 ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton(); 639 rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick); 640 rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover); 641 updateScreenPinningGestures(); 642 } 643 644 private boolean onHomeTouch(View v, MotionEvent event) { 645 if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) { 646 return true; 647 } 648 // If an incoming call is ringing, HOME is totally disabled. 649 // (The user is already on the InCallUI at this point, 650 // and his ONLY options are to answer or reject the call.) 651 switch (event.getAction()) { 652 case MotionEvent.ACTION_DOWN: 653 mHomeBlockedThisTouch = false; 654 TelecomManager telecomManager = 655 getContext().getSystemService(TelecomManager.class); 656 if (telecomManager != null && telecomManager.isRinging()) { 657 if (mStatusBar.isKeyguardShowing()) { 658 Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " + 659 "No heads up"); 660 mHomeBlockedThisTouch = true; 661 return true; 662 } 663 } 664 break; 665 case MotionEvent.ACTION_UP: 666 case MotionEvent.ACTION_CANCEL: 667 mStatusBar.awakenDreams(); 668 break; 669 } 670 return false; 671 } 672 673 private void onVerticalChanged(boolean isVertical) { 674 mStatusBar.setQsScrimEnabled(!isVertical); 675 } 676 677 private boolean onNavigationTouch(View v, MotionEvent event) { 678 mStatusBar.checkUserAutohide(event); 679 return false; 680 } 681 682 @VisibleForTesting 683 boolean onHomeLongClick(View v) { 684 if (shouldDisableNavbarGestures()) { 685 return false; 686 } 687 mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS); 688 mAssistManager.startAssist(new Bundle() /* args */); 689 mStatusBar.awakenDreams(); 690 691 if (mNavigationBarView != null) { 692 mNavigationBarView.abortCurrentGesture(); 693 } 694 return true; 695 } 696 697 // additional optimization when we have software system buttons - start loading the recent 698 // tasks on touch down 699 private boolean onRecentsTouch(View v, MotionEvent event) { 700 int action = event.getAction() & MotionEvent.ACTION_MASK; 701 if (action == MotionEvent.ACTION_DOWN) { 702 mCommandQueue.preloadRecentApps(); 703 } else if (action == MotionEvent.ACTION_CANCEL) { 704 mCommandQueue.cancelPreloadRecentApps(); 705 } else if (action == MotionEvent.ACTION_UP) { 706 if (!v.isPressed()) { 707 mCommandQueue.cancelPreloadRecentApps(); 708 } 709 } 710 return false; 711 } 712 713 private void onRecentsClick(View v) { 714 if (LatencyTracker.isEnabled(getContext())) { 715 LatencyTracker.getInstance(getContext()).onActionStart( 716 LatencyTracker.ACTION_TOGGLE_RECENTS); 717 } 718 mStatusBar.awakenDreams(); 719 mCommandQueue.toggleRecentApps(); 720 } 721 722 private boolean onLongPressBackHome(View v) { 723 return onLongPressNavigationButtons(v, R.id.back, R.id.home); 724 } 725 726 private boolean onLongPressBackRecents(View v) { 727 return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps); 728 } 729 730 /** 731 * This handles long-press of both back and recents/home. Back is the common button with 732 * combination of recents if it is visible or home if recents is invisible. 733 * They are handled together to capture them both being long-pressed 734 * at the same time to exit screen pinning (lock task). 735 * 736 * When accessibility mode is on, only a long-press from recents/home 737 * is required to exit. 738 * 739 * In all other circumstances we try to pass through long-press events 740 * for Back, so that apps can still use it. Which can be from two things. 741 * 1) Not currently in screen pinning (lock task). 742 * 2) Back is long-pressed without recents/home. 743 */ 744 private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) { 745 try { 746 boolean sendBackLongPress = false; 747 IActivityManager activityManager = ActivityManagerNative.getDefault(); 748 boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); 749 boolean inLockTaskMode = activityManager.isInLockTaskMode(); 750 if (inLockTaskMode && !touchExplorationEnabled) { 751 long time = System.currentTimeMillis(); 752 753 // If we recently long-pressed the other button then they were 754 // long-pressed 'together' 755 if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { 756 activityManager.stopSystemLockTaskMode(); 757 // When exiting refresh disabled flags. 758 mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); 759 return true; 760 } else if (v.getId() == btnId1) { 761 ButtonDispatcher button = btnId2 == R.id.recent_apps 762 ? mNavigationBarView.getRecentsButton() 763 : mNavigationBarView.getHomeButton(); 764 if (!button.getCurrentView().isPressed()) { 765 // If we aren't pressing recents/home right now then they presses 766 // won't be together, so send the standard long-press action. 767 sendBackLongPress = true; 768 } 769 } 770 mLastLockToAppLongPress = time; 771 } else { 772 // If this is back still need to handle sending the long-press event. 773 if (v.getId() == btnId1) { 774 sendBackLongPress = true; 775 } else if (touchExplorationEnabled && inLockTaskMode) { 776 // When in accessibility mode a long press that is recents/home (not back) 777 // should stop lock task. 778 activityManager.stopSystemLockTaskMode(); 779 // When exiting refresh disabled flags. 780 mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); 781 return true; 782 } else if (v.getId() == btnId2) { 783 return btnId2 == R.id.recent_apps 784 ? onLongPressRecents() 785 : onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView()); 786 } 787 } 788 if (sendBackLongPress) { 789 KeyButtonView keyButtonView = (KeyButtonView) v; 790 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 791 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 792 return true; 793 } 794 } catch (RemoteException e) { 795 Log.d(TAG, "Unable to reach activity manager", e); 796 } 797 return false; 798 } 799 800 private boolean onLongPressRecents() { 801 if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext()) 802 || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible() 803 || Recents.getConfiguration().isLowRamDevice) { 804 return false; 805 } 806 807 return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS, 808 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS); 809 } 810 811 private void onAccessibilityClick(View v) { 812 mAccessibilityManager.notifyAccessibilityButtonClicked(); 813 } 814 815 private boolean onAccessibilityLongClick(View v) { 816 Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); 817 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 818 v.getContext().startActivityAsUser(intent, UserHandle.CURRENT); 819 return true; 820 } 821 822 private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) { 823 int requestingServices = 0; 824 try { 825 if (Settings.Secure.getIntForUser(mContentResolver, 826 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 827 UserHandle.USER_CURRENT) == 1) { 828 requestingServices++; 829 } 830 } catch (Settings.SettingNotFoundException e) { 831 } 832 833 boolean feedbackEnabled = false; 834 // AccessibilityManagerService resolves services for the current user since the local 835 // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission 836 final List<AccessibilityServiceInfo> services = 837 accessibilityManager.getEnabledAccessibilityServiceList( 838 AccessibilityServiceInfo.FEEDBACK_ALL_MASK); 839 for (int i = services.size() - 1; i >= 0; --i) { 840 AccessibilityServiceInfo info = services.get(i); 841 if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { 842 requestingServices++; 843 } 844 845 if (info.feedbackType != 0 && info.feedbackType != 846 AccessibilityServiceInfo.FEEDBACK_GENERIC) { 847 feedbackEnabled = true; 848 } 849 } 850 851 mAccessibilityFeedbackEnabled = feedbackEnabled; 852 853 final boolean showAccessibilityButton = requestingServices >= 1; 854 final boolean targetSelection = requestingServices >= 2; 855 mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection); 856 } 857 858 private void onRotateSuggestionClick(View v) { 859 mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED); 860 mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion); 861 } 862 863 private boolean onRotateSuggestionHover(View v, MotionEvent event) { 864 final int action = event.getActionMasked(); 865 mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER) 866 || (action == MotionEvent.ACTION_HOVER_MOVE); 867 rescheduleRotationTimeout(true); 868 return false; // Must return false so a11y hover events are dispatched correctly. 869 } 870 871 // ----- Methods that StatusBar talks to (should be minimized) ----- 872 873 public void setLightBarController(LightBarController lightBarController) { 874 mLightBarController = lightBarController; 875 mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController()); 876 } 877 878 public boolean isSemiTransparent() { 879 return mNavigationBarMode == MODE_SEMI_TRANSPARENT; 880 } 881 882 public void disableAnimationsDuringHide(long delay) { 883 mNavigationBarView.setLayoutTransitionsEnabled(false); 884 mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true), 885 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 886 } 887 888 public BarTransitions getBarTransitions() { 889 return mNavigationBarView.getBarTransitions(); 890 } 891 892 public void checkNavBarModes() { 893 mStatusBar.checkBarMode(mNavigationBarMode, 894 mNavigationBarWindowState, mNavigationBarView.getBarTransitions()); 895 } 896 897 public void finishBarAnimations() { 898 mNavigationBarView.getBarTransitions().finishAnimations(); 899 } 900 901 private final AccessibilityServicesStateChangeListener mAccessibilityListener = 902 this::updateAccessibilityServicesState; 903 904 private class MagnificationContentObserver extends ContentObserver { 905 906 public MagnificationContentObserver(Handler handler) { 907 super(handler); 908 } 909 910 @Override 911 public void onChange(boolean selfChange) { 912 NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager); 913 } 914 } 915 916 private final Stub mRotationWatcher = new Stub() { 917 @Override 918 public void onRotationChanged(final int rotation) throws RemoteException { 919 // We need this to be scheduled as early as possible to beat the redrawing of 920 // window in response to the orientation change. 921 Handler h = getView().getHandler(); 922 Message msg = Message.obtain(h, () -> { 923 924 // If the screen rotation changes while locked, potentially update lock to flow with 925 // new screen rotation and hide any showing suggestions. 926 if (mRotationLockController.isRotationLocked()) { 927 if (shouldOverrideUserLockPrefs(rotation)) { 928 mRotationLockController.setRotationLockedAtAngle(true, rotation); 929 } 930 setRotateSuggestionButtonState(false, true); 931 } 932 933 if (mNavigationBarView != null 934 && mNavigationBarView.needsReorient(rotation)) { 935 repositionNavigationBar(); 936 } 937 }); 938 msg.setAsynchronous(true); 939 h.sendMessageAtFrontOfQueue(msg); 940 } 941 942 private boolean shouldOverrideUserLockPrefs(final int rotation) { 943 // Only override user prefs when returning to portrait. 944 // Don't let apps that force landscape or 180 alter user lock. 945 return rotation == Surface.ROTATION_0; 946 } 947 }; 948 949 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 950 @Override 951 public void onReceive(Context context, Intent intent) { 952 String action = intent.getAction(); 953 if (Intent.ACTION_SCREEN_OFF.equals(action) 954 || Intent.ACTION_SCREEN_ON.equals(action)) { 955 notifyNavigationBarScreenOn(); 956 } 957 } 958 }; 959 960 class TaskStackListenerImpl extends SysUiTaskStackChangeListener { 961 // Invalidate any rotation suggestion on task change or activity orientation change 962 // Note: all callbacks happen on main thread 963 964 @Override 965 public void onTaskStackChanged() { 966 setRotateSuggestionButtonState(false); 967 } 968 969 @Override 970 public void onTaskRemoved(int taskId) { 971 setRotateSuggestionButtonState(false); 972 } 973 974 @Override 975 public void onTaskMovedToFront(int taskId) { 976 setRotateSuggestionButtonState(false); 977 } 978 979 @Override 980 public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { 981 setRotateSuggestionButtonState(false); 982 } 983 } 984 985 public static View create(Context context, FragmentListener listener) { 986 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 987 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 988 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 989 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 990 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 991 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 992 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 993 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 994 | WindowManager.LayoutParams.FLAG_SLIPPERY, 995 PixelFormat.TRANSLUCENT); 996 lp.token = new Binder(); 997 lp.setTitle("NavigationBar"); 998 lp.accessibilityTitle = context.getString(R.string.nav_bar); 999 lp.windowAnimations = 0; 1000 1001 View navigationBarView = LayoutInflater.from(context).inflate( 1002 R.layout.navigation_bar_window, null); 1003 1004 if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); 1005 if (navigationBarView == null) return null; 1006 1007 context.getSystemService(WindowManager.class).addView(navigationBarView, lp); 1008 FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView); 1009 NavigationBarFragment fragment = new NavigationBarFragment(); 1010 fragmentHost.getFragmentManager().beginTransaction() 1011 .replace(R.id.navigation_bar_frame, fragment, TAG) 1012 .commit(); 1013 fragmentHost.addTagListener(TAG, listener); 1014 return navigationBarView; 1015 } 1016} 1017