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