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