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