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