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