NavigationBarFragment.java revision 9c7844cb91b43929d0a86b1c90aa1efb37f5463a
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15package com.android.systemui.statusbar.phone; 16 17import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; 18import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; 19import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; 20import static android.app.StatusBarManager.windowStateToString; 21 22import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; 23import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG_WINDOW_STATE; 24import static com.android.systemui.statusbar.phone.PhoneStatusBar.dumpBarTransitions; 25 26import android.annotation.Nullable; 27import android.app.ActivityManager; 28import android.app.ActivityManagerNative; 29import android.app.Fragment; 30import android.app.IActivityManager; 31import android.app.StatusBarManager; 32import android.content.BroadcastReceiver; 33import android.content.Context; 34import android.content.Intent; 35import android.content.IntentFilter; 36import android.content.res.Configuration; 37import android.graphics.PixelFormat; 38import android.graphics.Rect; 39import android.inputmethodservice.InputMethodService; 40import android.os.Binder; 41import android.os.Bundle; 42import android.os.Handler; 43import android.os.IBinder; 44import android.os.Message; 45import android.os.PowerManager; 46import android.os.RemoteException; 47import android.os.UserHandle; 48import android.support.annotation.VisibleForTesting; 49import android.telecom.TelecomManager; 50import android.text.TextUtils; 51import android.util.Log; 52import android.view.IRotationWatcher.Stub; 53import android.view.KeyEvent; 54import android.view.LayoutInflater; 55import android.view.MotionEvent; 56import android.view.View; 57import android.view.ViewGroup; 58import android.view.WindowManager; 59import android.view.WindowManager.LayoutParams; 60import android.view.WindowManagerGlobal; 61import android.view.accessibility.AccessibilityEvent; 62import android.view.accessibility.AccessibilityManager; 63 64import com.android.internal.logging.MetricsLogger; 65import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 66import com.android.keyguard.LatencyTracker; 67import com.android.systemui.Dependency; 68import com.android.systemui.R; 69import com.android.systemui.SysUiServiceProvider; 70import com.android.systemui.SystemUIApplication; 71import com.android.systemui.assist.AssistManager; 72import com.android.systemui.fragments.FragmentHostManager; 73import com.android.systemui.fragments.FragmentHostManager.FragmentListener; 74import com.android.systemui.recents.Recents; 75import com.android.systemui.stackdivider.Divider; 76import com.android.systemui.statusbar.CommandQueue; 77import com.android.systemui.statusbar.CommandQueue.Callbacks; 78import com.android.systemui.statusbar.policy.KeyButtonView; 79import com.android.systemui.statusbar.stack.StackStateAnimator; 80 81import java.io.FileDescriptor; 82import java.io.PrintWriter; 83import java.util.Locale; 84 85/** 86 * Fragment containing the NavigationBarFragment. Contains logic for what happens 87 * on clicks and view states of the nav bar. 88 */ 89public class NavigationBarFragment extends Fragment implements Callbacks { 90 91 private static final String TAG = "NavigationBar"; 92 private static final boolean DEBUG = false; 93 private static final String EXTRA_DISABLE_STATE = "disabled_state"; 94 95 /** Allow some time inbetween the long press for back and recents. */ 96 private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; 97 98 protected NavigationBarView mNavigationBarView = null; 99 protected AssistManager mAssistManager; 100 101 private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; 102 103 private int mNavigationIconHints = 0; 104 private int mNavigationBarMode; 105 protected AccessibilityManager mAccessibilityManager; 106 107 private int mDisabledFlags1; 108 private PhoneStatusBar mPhoneStatusBar; 109 private Recents mRecents; 110 private Divider mDivider; 111 private WindowManager mWindowManager; 112 private CommandQueue mCommandQueue; 113 private long mLastLockToAppLongPress; 114 115 private Locale mLocale; 116 private int mLayoutDirection; 117 118 private int mSystemUiVisibility; 119 private LightBarController mLightBarController; 120 private boolean mKeyguardGoingAway; 121 122 public boolean mHomeBlockedThisTouch; 123 124 // ----- Fragment Lifecycle Callbacks ----- 125 126 @Override 127 public void onCreate(@Nullable Bundle savedInstanceState) { 128 super.onCreate(savedInstanceState); 129 mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class); 130 mCommandQueue.addCallbacks(this); 131 mPhoneStatusBar = SysUiServiceProvider.getComponent(getContext(), PhoneStatusBar.class); 132 mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class); 133 mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class); 134 mWindowManager = getContext().getSystemService(WindowManager.class); 135 mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); 136 if (savedInstanceState != null) { 137 mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); 138 } 139 mAssistManager = Dependency.get(AssistManager.class); 140 141 try { 142 WindowManagerGlobal.getWindowManagerService() 143 .watchRotation(mRotationWatcher); 144 } catch (RemoteException e) { 145 throw e.rethrowFromSystemServer(); 146 } 147 } 148 149 @Override 150 public void onDestroy() { 151 super.onDestroy(); 152 mCommandQueue.removeCallbacks(this); 153 try { 154 WindowManagerGlobal.getWindowManagerService() 155 .removeRotationWatcher(mRotationWatcher); 156 } catch (RemoteException e) { 157 throw e.rethrowFromSystemServer(); 158 } 159 } 160 161 @Override 162 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 163 Bundle savedInstanceState) { 164 return inflater.inflate(R.layout.navigation_bar, container, false); 165 } 166 167 @Override 168 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 169 super.onViewCreated(view, savedInstanceState); 170 mNavigationBarView = (NavigationBarView) view; 171 172 mNavigationBarView.setDisabledFlags(mDisabledFlags1); 173 mNavigationBarView.setComponents(mRecents, mDivider); 174 mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); 175 mNavigationBarView.setOnTouchListener(this::onNavigationTouch); 176 if (savedInstanceState != null) { 177 mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState); 178 } 179 180 prepareNavigationBarView(); 181 checkNavBarModes(); 182 183 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 184 filter.addAction(Intent.ACTION_SCREEN_ON); 185 getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); 186 PowerManager pm = getContext().getSystemService(PowerManager.class); 187 notifyNavigationBarScreenOn(pm.isScreenOn()); 188 } 189 190 @Override 191 public void onDestroyView() { 192 super.onDestroyView(); 193 getContext().unregisterReceiver(mBroadcastReceiver); 194 } 195 196 @Override 197 public void onSaveInstanceState(Bundle outState) { 198 super.onSaveInstanceState(outState); 199 outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1); 200 if (mNavigationBarView != null) { 201 mNavigationBarView.getLightTransitionsController().saveState(outState); 202 } 203 } 204 205 @Override 206 public void onConfigurationChanged(Configuration newConfig) { 207 super.onConfigurationChanged(newConfig); 208 final Locale locale = getContext().getResources().getConfiguration().locale; 209 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 210 if (!locale.equals(mLocale) || ld != mLayoutDirection) { 211 if (DEBUG) { 212 Log.v(TAG, String.format( 213 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 214 locale, ld)); 215 } 216 mLocale = locale; 217 mLayoutDirection = ld; 218 refreshLayout(ld); 219 } 220 repositionNavigationBar(); 221 } 222 223 @Override 224 public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { 225 if (mNavigationBarView != null) { 226 pw.print(" mNavigationBarWindowState="); 227 pw.println(windowStateToString(mNavigationBarWindowState)); 228 pw.print(" mNavigationBarMode="); 229 pw.println(BarTransitions.modeToString(mNavigationBarMode)); 230 dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); 231 } 232 233 pw.print(" mNavigationBarView="); 234 if (mNavigationBarView == null) { 235 pw.println("null"); 236 } else { 237 mNavigationBarView.dump(fd, pw, args); 238 } 239 } 240 241 // ----- CommandQueue Callbacks ----- 242 243 @Override 244 public void setImeWindowStatus(IBinder token, int vis, int backDisposition, 245 boolean showImeSwitcher) { 246 boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; 247 int hints = mNavigationIconHints; 248 if ((backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || imeShown) { 249 hints |= NAVIGATION_HINT_BACK_ALT; 250 } else { 251 hints &= ~NAVIGATION_HINT_BACK_ALT; 252 } 253 if (showImeSwitcher) { 254 hints |= NAVIGATION_HINT_IME_SHOWN; 255 } else { 256 hints &= ~NAVIGATION_HINT_IME_SHOWN; 257 } 258 if (hints == mNavigationIconHints) return; 259 260 mNavigationIconHints = hints; 261 262 if (mNavigationBarView != null) { 263 mNavigationBarView.setNavigationIconHints(hints); 264 } 265 mPhoneStatusBar.checkBarModes(); 266 } 267 268 @Override 269 public void topAppWindowChanged(boolean showMenu) { 270 if (mNavigationBarView != null) { 271 mNavigationBarView.setMenuVisibility(showMenu); 272 } 273 } 274 275 @Override 276 public void setWindowState(int window, int state) { 277 if (mNavigationBarView != null 278 && window == StatusBarManager.WINDOW_NAVIGATION_BAR 279 && mNavigationBarWindowState != state) { 280 mNavigationBarWindowState = state; 281 if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state)); 282 } 283 } 284 285 @Override 286 public void appTransitionPending() { 287 mNavigationBarView.getLightTransitionsController().appTransitionPending(); 288 } 289 290 @Override 291 public void appTransitionCancelled() { 292 mNavigationBarView.getLightTransitionsController().appTransitionCancelled(); 293 } 294 295 @Override 296 public void appTransitionStarting(long startTime, long duration) { 297 if (mKeyguardGoingAway) return; 298 doAppTransitionStarting(startTime, duration); 299 } 300 301 /** 302 * Calls appTransitionStarting for the nav bar regardless of whether keyguard is going away. 303 * public so PhoneStatusBar can force this when needed. 304 */ 305 public void doAppTransitionStarting(long startTime, long duration) { 306 mNavigationBarView.getLightTransitionsController().appTransitionStarting(startTime, 307 duration); 308 } 309 310 // Injected from PhoneStatusBar at creation. 311 public void setCurrentSysuiVisibility(int systemUiVisibility) { 312 mSystemUiVisibility = systemUiVisibility; 313 mNavigationBarMode = mPhoneStatusBar.computeBarMode(0, mSystemUiVisibility, 314 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT, 315 View.NAVIGATION_BAR_TRANSPARENT); 316 checkNavBarModes(); 317 mPhoneStatusBar.touchAutoHide(); 318 mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */, 319 true /* nbModeChanged */, mNavigationBarMode); 320 } 321 322 @Override 323 public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, 324 int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { 325 final int oldVal = mSystemUiVisibility; 326 final int newVal = (oldVal & ~mask) | (vis & mask); 327 final int diff = newVal ^ oldVal; 328 boolean nbModeChanged = false; 329 if (diff != 0) { 330 mSystemUiVisibility = newVal; 331 332 // update navigation bar mode 333 final int nbMode = getView() == null 334 ? -1 : mPhoneStatusBar.computeBarMode(oldVal, newVal, 335 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT, 336 View.NAVIGATION_BAR_TRANSPARENT); 337 nbModeChanged = nbMode != -1; 338 if (nbModeChanged) { 339 if (mNavigationBarMode != nbMode) { 340 mNavigationBarMode = nbMode; 341 checkNavBarModes(); 342 } 343 mPhoneStatusBar.touchAutoHide(); 344 } 345 } 346 347 mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged, 348 mNavigationBarMode); 349 } 350 351 @Override 352 public void disable(int state1, int state2, boolean animate) { 353 // All navigation bar flags are in state1. 354 int masked = state1 & (StatusBarManager.DISABLE_HOME 355 | StatusBarManager.DISABLE_RECENT 356 | StatusBarManager.DISABLE_BACK 357 | StatusBarManager.DISABLE_SEARCH); 358 if (masked != mDisabledFlags1) { 359 mDisabledFlags1 = masked; 360 if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1); 361 } 362 } 363 364 // ----- Internal stuffz ----- 365 366 private void refreshLayout(int layoutDirection) { 367 if (mNavigationBarView != null) { 368 mNavigationBarView.setLayoutDirection(layoutDirection); 369 } 370 } 371 372 private boolean shouldDisableNavbarGestures() { 373 return !mPhoneStatusBar.isDeviceProvisioned() 374 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0; 375 } 376 377 private void repositionNavigationBar() { 378 if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; 379 380 prepareNavigationBarView(); 381 382 mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(), 383 ((View) mNavigationBarView.getParent()).getLayoutParams()); 384 } 385 386 private void notifyNavigationBarScreenOn(boolean screenOn) { 387 mNavigationBarView.notifyScreenOn(screenOn); 388 } 389 390 private void prepareNavigationBarView() { 391 mNavigationBarView.reorient(); 392 393 ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); 394 recentsButton.setOnClickListener(this::onRecentsClick); 395 recentsButton.setOnTouchListener(this::onRecentsTouch); 396 recentsButton.setLongClickable(true); 397 recentsButton.setOnLongClickListener(this::onLongPressBackRecents); 398 399 ButtonDispatcher backButton = mNavigationBarView.getBackButton(); 400 backButton.setLongClickable(true); 401 backButton.setOnLongClickListener(this::onLongPressBackRecents); 402 403 ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); 404 homeButton.setOnTouchListener(this::onHomeTouch); 405 homeButton.setOnLongClickListener(this::onHomeLongClick); 406 } 407 408 private boolean onHomeTouch(View v, MotionEvent event) { 409 if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) { 410 return true; 411 } 412 // If an incoming call is ringing, HOME is totally disabled. 413 // (The user is already on the InCallUI at this point, 414 // and his ONLY options are to answer or reject the call.) 415 switch (event.getAction()) { 416 case MotionEvent.ACTION_DOWN: 417 mHomeBlockedThisTouch = false; 418 TelecomManager telecomManager = 419 getContext().getSystemService(TelecomManager.class); 420 if (telecomManager != null && telecomManager.isRinging()) { 421 if (mPhoneStatusBar.isKeyguardShowing()) { 422 Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " + 423 "No heads up"); 424 mHomeBlockedThisTouch = true; 425 return true; 426 } 427 } 428 break; 429 case MotionEvent.ACTION_UP: 430 case MotionEvent.ACTION_CANCEL: 431 mPhoneStatusBar.awakenDreams(); 432 break; 433 } 434 return false; 435 } 436 437 private void onVerticalChanged(boolean isVertical) { 438 mPhoneStatusBar.setQsScrimEnabled(!isVertical); 439 } 440 441 private boolean onNavigationTouch(View v, MotionEvent event) { 442 mPhoneStatusBar.checkUserAutohide(v, event); 443 return false; 444 } 445 446 @VisibleForTesting 447 boolean onHomeLongClick(View v) { 448 if (shouldDisableNavbarGestures()) { 449 return false; 450 } 451 MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS); 452 mAssistManager.startAssist(new Bundle() /* args */); 453 mPhoneStatusBar.awakenDreams(); 454 if (mNavigationBarView != null) { 455 mNavigationBarView.abortCurrentGesture(); 456 } 457 return true; 458 } 459 460 // additional optimization when we have software system buttons - start loading the recent 461 // tasks on touch down 462 private boolean onRecentsTouch(View v, MotionEvent event) { 463 int action = event.getAction() & MotionEvent.ACTION_MASK; 464 if (action == MotionEvent.ACTION_DOWN) { 465 mCommandQueue.preloadRecentApps(); 466 } else if (action == MotionEvent.ACTION_CANCEL) { 467 mCommandQueue.cancelPreloadRecentApps(); 468 } else if (action == MotionEvent.ACTION_UP) { 469 if (!v.isPressed()) { 470 mCommandQueue.cancelPreloadRecentApps(); 471 } 472 } 473 return false; 474 } 475 476 private void onRecentsClick(View v) { 477 if (LatencyTracker.isEnabled(getContext())) { 478 LatencyTracker.getInstance(getContext()).onActionStart( 479 LatencyTracker.ACTION_TOGGLE_RECENTS); 480 } 481 mPhoneStatusBar.awakenDreams(); 482 mCommandQueue.toggleRecentApps(); 483 } 484 485 /** 486 * This handles long-press of both back and recents. They are 487 * handled together to capture them both being long-pressed 488 * at the same time to exit screen pinning (lock task). 489 * 490 * When accessibility mode is on, only a long-press from recents 491 * is required to exit. 492 * 493 * In all other circumstances we try to pass through long-press events 494 * for Back, so that apps can still use it. Which can be from two things. 495 * 1) Not currently in screen pinning (lock task). 496 * 2) Back is long-pressed without recents. 497 */ 498 private boolean onLongPressBackRecents(View v) { 499 try { 500 boolean sendBackLongPress = false; 501 IActivityManager activityManager = ActivityManagerNative.getDefault(); 502 boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); 503 boolean inLockTaskMode = activityManager.isInLockTaskMode(); 504 if (inLockTaskMode && !touchExplorationEnabled) { 505 long time = System.currentTimeMillis(); 506 // If we recently long-pressed the other button then they were 507 // long-pressed 'together' 508 if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { 509 activityManager.stopLockTaskMode(); 510 // When exiting refresh disabled flags. 511 mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); 512 return true; 513 } else if ((v.getId() == R.id.back) 514 && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) { 515 // If we aren't pressing recents right now then they presses 516 // won't be together, so send the standard long-press action. 517 sendBackLongPress = true; 518 } 519 mLastLockToAppLongPress = time; 520 } else { 521 // If this is back still need to handle sending the long-press event. 522 if (v.getId() == R.id.back) { 523 sendBackLongPress = true; 524 } else if (touchExplorationEnabled && inLockTaskMode) { 525 // When in accessibility mode a long press that is recents (not back) 526 // should stop lock task. 527 activityManager.stopLockTaskMode(); 528 // When exiting refresh disabled flags. 529 mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); 530 return true; 531 } else if (v.getId() == R.id.recent_apps) { 532 return onLongPressRecents(); 533 } 534 } 535 if (sendBackLongPress) { 536 KeyButtonView keyButtonView = (KeyButtonView) v; 537 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 538 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 539 return true; 540 } 541 } catch (RemoteException e) { 542 Log.d(TAG, "Unable to reach activity manager", e); 543 } 544 return false; 545 } 546 547 private boolean onLongPressRecents() { 548 if (mRecents == null || !ActivityManager.supportsMultiWindow() 549 || !mDivider.getView().getSnapAlgorithm() 550 .isSplitScreenFeasible()) { 551 return false; 552 } 553 554 return mPhoneStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS, 555 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS); 556 } 557 558 // ----- Methods that PhoneStatusBar talks to (should be minimized) ----- 559 560 public void setLightBarController(LightBarController lightBarController) { 561 mLightBarController = lightBarController; 562 mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController()); 563 } 564 565 public boolean isSemiTransparent() { 566 return mNavigationBarMode == MODE_SEMI_TRANSPARENT; 567 } 568 569 public void onKeyguardOccludedChanged(boolean keyguardOccluded) { 570 mNavigationBarView.onKeyguardOccludedChanged(keyguardOccluded); 571 } 572 573 public void disableAnimationsDuringHide(long delay) { 574 mNavigationBarView.setLayoutTransitionsEnabled(false); 575 mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true), 576 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 577 } 578 579 public void setKeyguardGoingAway(boolean keyguardGoingAway) { 580 mKeyguardGoingAway = keyguardGoingAway; 581 } 582 583 public BarTransitions getBarTransitions() { 584 return mNavigationBarView.getBarTransitions(); 585 } 586 587 public void checkNavBarModes() { 588 mPhoneStatusBar.checkBarMode(mNavigationBarMode, 589 mNavigationBarWindowState, mNavigationBarView.getBarTransitions()); 590 } 591 592 public void finishBarAnimations() { 593 mNavigationBarView.getBarTransitions().finishAnimations(); 594 } 595 596 private final Stub mRotationWatcher = new Stub() { 597 @Override 598 public void onRotationChanged(int rotation) throws RemoteException { 599 // We need this to be scheduled as early as possible to beat the redrawing of 600 // window in response to the orientation change. 601 Handler h = getView().getHandler(); 602 Message msg = Message.obtain(h, () -> { 603 if (mNavigationBarView != null 604 && mNavigationBarView.needsReorient(rotation)) { 605 repositionNavigationBar(); 606 } 607 }); 608 msg.setAsynchronous(true); 609 h.sendMessageAtFrontOfQueue(msg); 610 } 611 }; 612 613 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 614 @Override 615 public void onReceive(Context context, Intent intent) { 616 String action = intent.getAction(); 617 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 618 notifyNavigationBarScreenOn(false); 619 } else if (Intent.ACTION_SCREEN_ON.equals(action)) { 620 notifyNavigationBarScreenOn(true); 621 } 622 } 623 }; 624 625 public static View create(Context context, FragmentListener listener) { 626 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 627 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 628 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 629 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 630 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 631 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 632 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 633 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 634 | WindowManager.LayoutParams.FLAG_SLIPPERY, 635 PixelFormat.TRANSLUCENT); 636 lp.token = new Binder(); 637 // this will allow the navbar to run in an overlay on devices that support this 638 if (ActivityManager.isHighEndGfx()) { 639 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 640 } 641 lp.setTitle("NavigationBar"); 642 lp.windowAnimations = 0; 643 644 View navigationBarView = LayoutInflater.from(context).inflate( 645 R.layout.navigation_bar_window, null); 646 647 if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); 648 if (navigationBarView == null) return null; 649 650 context.getSystemService(WindowManager.class).addView(navigationBarView, lp); 651 FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView); 652 NavigationBarFragment fragment = new NavigationBarFragment(); 653 fragmentHost.getFragmentManager().beginTransaction() 654 .replace(R.id.navigation_bar_frame, fragment, TAG) 655 .commit(); 656 fragmentHost.addTagListener(TAG, listener); 657 return navigationBarView; 658 } 659} 660