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