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