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