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