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