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