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