NavigationBarFragment.java revision a48cf192f37bc94740594a1731716403bb03ec39
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.StatusBar.DEBUG_WINDOW_STATE;
24import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
25
26import android.accessibilityservice.AccessibilityServiceInfo;
27import android.animation.Animator;
28import android.animation.AnimatorListenerAdapter;
29import android.animation.ObjectAnimator;
30import android.annotation.Nullable;
31import android.app.ActivityManager;
32import android.app.ActivityManagerNative;
33import android.app.Fragment;
34import android.app.IActivityManager;
35import android.app.StatusBarManager;
36import android.content.BroadcastReceiver;
37import android.content.ContentResolver;
38import android.content.Context;
39import android.content.Intent;
40import android.content.IntentFilter;
41import android.content.res.Configuration;
42import android.database.ContentObserver;
43import android.graphics.PixelFormat;
44import android.graphics.Rect;
45import android.graphics.drawable.AnimatedVectorDrawable;
46import android.inputmethodservice.InputMethodService;
47import android.os.Binder;
48import android.os.Bundle;
49import android.os.Handler;
50import android.os.IBinder;
51import android.os.Message;
52import android.os.RemoteException;
53import android.os.UserHandle;
54import android.provider.Settings;
55import android.support.annotation.VisibleForTesting;
56import android.telecom.TelecomManager;
57import android.text.TextUtils;
58import android.util.Log;
59import android.view.IRotationWatcher.Stub;
60import android.view.KeyEvent;
61import android.view.LayoutInflater;
62import android.view.MotionEvent;
63import android.view.Surface;
64import android.view.View;
65import android.view.ViewGroup;
66import android.view.WindowManager;
67import android.view.WindowManager.LayoutParams;
68import android.view.WindowManagerGlobal;
69import android.view.accessibility.AccessibilityEvent;
70import android.view.accessibility.AccessibilityManager;
71import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
72
73import com.android.internal.logging.MetricsLogger;
74import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
75import com.android.internal.util.LatencyTracker;
76import com.android.systemui.Dependency;
77import com.android.systemui.OverviewProxyService;
78import com.android.systemui.Interpolators;
79import com.android.systemui.R;
80import com.android.systemui.SysUiServiceProvider;
81import com.android.systemui.assist.AssistManager;
82import com.android.systemui.fragments.FragmentHostManager;
83import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
84import com.android.systemui.recents.Recents;
85import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
86import com.android.systemui.shared.system.ActivityManagerWrapper;
87import com.android.systemui.stackdivider.Divider;
88import com.android.systemui.statusbar.CommandQueue;
89import com.android.systemui.statusbar.CommandQueue.Callbacks;
90import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
91import com.android.systemui.statusbar.policy.KeyButtonDrawable;
92import com.android.systemui.statusbar.policy.KeyButtonView;
93import com.android.systemui.statusbar.policy.RotationLockController;
94import com.android.systemui.statusbar.stack.StackStateAnimator;
95
96import java.io.FileDescriptor;
97import java.io.PrintWriter;
98import java.util.List;
99import java.util.Locale;
100
101/**
102 * Fragment containing the NavigationBarFragment. Contains logic for what happens
103 * on clicks and view states of the nav bar.
104 */
105public class NavigationBarFragment extends Fragment implements Callbacks {
106
107    public static final String TAG = "NavigationBar";
108    private static final boolean DEBUG = false;
109    private static final String EXTRA_DISABLE_STATE = "disabled_state";
110
111    /** Allow some time inbetween the long press for back and recents. */
112    private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
113
114    protected NavigationBarView mNavigationBarView = null;
115    protected AssistManager mAssistManager;
116
117    private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
118
119    private int mNavigationIconHints = 0;
120    private int mNavigationBarMode;
121    private boolean mAccessibilityFeedbackEnabled;
122    private AccessibilityManager mAccessibilityManager;
123    private MagnificationContentObserver mMagnificationObserver;
124    private ContentResolver mContentResolver;
125    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
126
127    private int mDisabledFlags1;
128    private StatusBar mStatusBar;
129    private Recents mRecents;
130    private Divider mDivider;
131    private WindowManager mWindowManager;
132    private CommandQueue mCommandQueue;
133    private long mLastLockToAppLongPress;
134
135    private Locale mLocale;
136    private int mLayoutDirection;
137
138    private int mSystemUiVisibility;
139    private LightBarController mLightBarController;
140
141    private OverviewProxyService mOverviewProxyService;
142
143    public boolean mHomeBlockedThisTouch;
144
145    private int mLastRotationSuggestion;
146    private boolean mHoveringRotationSuggestion;
147    private RotationLockController mRotationLockController;
148    private TaskStackListenerImpl mTaskStackListener;
149
150    private final Runnable mRemoveRotationProposal = () -> safeSetRotationButtonState(false);
151    private Animator mRotateShowAnimator;
152    private Animator mRotateHideAnimator;
153
154
155    // ----- Fragment Lifecycle Callbacks -----
156
157    @Override
158    public void onCreate(@Nullable Bundle savedInstanceState) {
159        super.onCreate(savedInstanceState);
160        mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
161        mCommandQueue.addCallbacks(this);
162        mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
163        mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
164        mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
165        mWindowManager = getContext().getSystemService(WindowManager.class);
166        mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
167        Dependency.get(AccessibilityManagerWrapper.class).addCallback(
168                mAccessibilityListener);
169        mContentResolver = getContext().getContentResolver();
170        mMagnificationObserver = new MagnificationContentObserver(
171                getContext().getMainThreadHandler());
172        mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
173                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
174                mMagnificationObserver, UserHandle.USER_ALL);
175
176        if (savedInstanceState != null) {
177            mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
178        }
179        mAssistManager = Dependency.get(AssistManager.class);
180        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
181
182        try {
183            WindowManagerGlobal.getWindowManagerService()
184                    .watchRotation(mRotationWatcher, getContext().getDisplay().getDisplayId());
185        } catch (RemoteException e) {
186            throw e.rethrowFromSystemServer();
187        }
188
189        mRotationLockController = Dependency.get(RotationLockController.class);
190
191        // Register the task stack listener
192        mTaskStackListener = new TaskStackListenerImpl();
193        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
194    }
195
196    @Override
197    public void onDestroy() {
198        super.onDestroy();
199        mCommandQueue.removeCallbacks(this);
200        Dependency.get(AccessibilityManagerWrapper.class).removeCallback(
201                mAccessibilityListener);
202        mContentResolver.unregisterContentObserver(mMagnificationObserver);
203        try {
204            WindowManagerGlobal.getWindowManagerService()
205                    .removeRotationWatcher(mRotationWatcher);
206        } catch (RemoteException e) {
207            throw e.rethrowFromSystemServer();
208        }
209
210        // Unregister the task stack listener
211        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
212    }
213
214    @Override
215    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
216            Bundle savedInstanceState) {
217        return inflater.inflate(R.layout.navigation_bar, container, false);
218    }
219
220    @Override
221    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
222        super.onViewCreated(view, savedInstanceState);
223        mNavigationBarView = (NavigationBarView) view;
224
225        mNavigationBarView.setDisabledFlags(mDisabledFlags1);
226        mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
227        mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
228        mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
229        if (savedInstanceState != null) {
230            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
231        }
232
233        prepareNavigationBarView();
234        checkNavBarModes();
235
236        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
237        filter.addAction(Intent.ACTION_SCREEN_ON);
238        getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
239        notifyNavigationBarScreenOn();
240    }
241
242    @Override
243    public void onDestroyView() {
244        super.onDestroyView();
245        mNavigationBarView.getLightTransitionsController().destroy(getContext());
246        getContext().unregisterReceiver(mBroadcastReceiver);
247    }
248
249    @Override
250    public void onSaveInstanceState(Bundle outState) {
251        super.onSaveInstanceState(outState);
252        outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
253        if (mNavigationBarView != null) {
254            mNavigationBarView.getLightTransitionsController().saveState(outState);
255        }
256    }
257
258    @Override
259    public void onConfigurationChanged(Configuration newConfig) {
260        super.onConfigurationChanged(newConfig);
261        final Locale locale = getContext().getResources().getConfiguration().locale;
262        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
263        if (!locale.equals(mLocale) || ld != mLayoutDirection) {
264            if (DEBUG) {
265                Log.v(TAG, String.format(
266                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
267                        locale, ld));
268            }
269            mLocale = locale;
270            mLayoutDirection = ld;
271            refreshLayout(ld);
272        }
273        repositionNavigationBar();
274    }
275
276    @Override
277    public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
278        if (mNavigationBarView != null) {
279            pw.print("  mNavigationBarWindowState=");
280            pw.println(windowStateToString(mNavigationBarWindowState));
281            pw.print("  mNavigationBarMode=");
282            pw.println(BarTransitions.modeToString(mNavigationBarMode));
283            dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
284        }
285
286        pw.print("  mNavigationBarView=");
287        if (mNavigationBarView == null) {
288            pw.println("null");
289        } else {
290            mNavigationBarView.dump(fd, pw, args);
291        }
292    }
293
294    // ----- CommandQueue Callbacks -----
295
296    @Override
297    public void setImeWindowStatus(IBinder token, int vis, int backDisposition,
298            boolean showImeSwitcher) {
299        boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
300        int hints = mNavigationIconHints;
301        if (imeShown && backDisposition != InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS) {
302            hints |= NAVIGATION_HINT_BACK_ALT;
303        } else {
304            hints &= ~NAVIGATION_HINT_BACK_ALT;
305        }
306        if (showImeSwitcher) {
307            hints |= NAVIGATION_HINT_IME_SHOWN;
308        } else {
309            hints &= ~NAVIGATION_HINT_IME_SHOWN;
310        }
311        if (hints == mNavigationIconHints) return;
312
313        mNavigationIconHints = hints;
314
315        if (mNavigationBarView != null) {
316            mNavigationBarView.setNavigationIconHints(hints);
317        }
318        mStatusBar.checkBarModes();
319    }
320
321    @Override
322    public void topAppWindowChanged(boolean showMenu) {
323        if (mNavigationBarView != null) {
324            mNavigationBarView.setMenuVisibility(showMenu);
325        }
326    }
327
328    @Override
329    public void setWindowState(int window, int state) {
330        if (mNavigationBarView != null
331                && window == StatusBarManager.WINDOW_NAVIGATION_BAR
332                && mNavigationBarWindowState != state) {
333            mNavigationBarWindowState = state;
334            if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
335        }
336    }
337
338    @Override
339    public void onRotationProposal(final int rotation, boolean isValid) {
340        // This method will be called on rotation suggestion changes even if the proposed rotation
341        // is not valid for the top app. Use invalid rotation choices as a signal to remove the
342        // rotate button if shown.
343
344        if (!isValid) {
345            safeSetRotationButtonState(false);
346            return;
347        }
348
349        if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
350            // Use this as a signal to remove any current suggestions
351            getView().getHandler().removeCallbacks(mRemoveRotationProposal);
352            safeSetRotationButtonState(false);
353        } else {
354            mLastRotationSuggestion = rotation; // Remember rotation for click
355            safeSetRotationButtonState(true);
356            rescheduleRotationTimeout(false);
357            mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
358        }
359    }
360
361    private void safeSetRotationButtonState(boolean vis) {
362        if (mNavigationBarView != null) mNavigationBarView.setRotateSuggestionButtonState(vis);
363    }
364
365    private void safeSetRotationButtonState(boolean vis, boolean force) {
366        if (mNavigationBarView != null) {
367            mNavigationBarView.setRotateSuggestionButtonState(vis, force);
368        }
369    }
370
371    private void rescheduleRotationTimeout(final boolean reasonHover) {
372        // May be called due to a new rotation proposal or a change in hover state
373        if (reasonHover) {
374            // Don't reschedule if a hide animator is running
375            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
376                return;
377            }
378            // Don't reschedule if not visible
379            if (mNavigationBarView.getRotateSuggestionButton().getVisibility() != View.VISIBLE) {
380                return;
381            }
382        }
383
384        Handler h = getView().getHandler();
385        h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
386        h.postDelayed(mRemoveRotationProposal,
387                computeRotationProposalTimeout()); // Schedule timeout
388    }
389
390    private int computeRotationProposalTimeout() {
391        if (mAccessibilityFeedbackEnabled) return 20000;
392        if (mHoveringRotationSuggestion) return 16000;
393        return 6000;
394    }
395
396    // Injected from StatusBar at creation.
397    public void setCurrentSysuiVisibility(int systemUiVisibility) {
398        mSystemUiVisibility = systemUiVisibility;
399        mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
400                View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
401                View.NAVIGATION_BAR_TRANSPARENT);
402        checkNavBarModes();
403        mStatusBar.touchAutoHide();
404        mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
405                true /* nbModeChanged */, mNavigationBarMode);
406    }
407
408    @Override
409    public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
410            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
411        final int oldVal = mSystemUiVisibility;
412        final int newVal = (oldVal & ~mask) | (vis & mask);
413        final int diff = newVal ^ oldVal;
414        boolean nbModeChanged = false;
415        if (diff != 0) {
416            mSystemUiVisibility = newVal;
417
418            // update navigation bar mode
419            final int nbMode = getView() == null
420                    ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
421                    View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
422                    View.NAVIGATION_BAR_TRANSPARENT);
423            nbModeChanged = nbMode != -1;
424            if (nbModeChanged) {
425                if (mNavigationBarMode != nbMode) {
426                    mNavigationBarMode = nbMode;
427                    checkNavBarModes();
428                }
429                mStatusBar.touchAutoHide();
430            }
431        }
432
433        mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged,
434                mNavigationBarMode);
435    }
436
437    @Override
438    public void disable(int state1, int state2, boolean animate) {
439        // All navigation bar flags are in state1.
440        int masked = state1 & (StatusBarManager.DISABLE_HOME
441                | StatusBarManager.DISABLE_RECENT
442                | StatusBarManager.DISABLE_BACK
443                | StatusBarManager.DISABLE_SEARCH);
444        if (masked != mDisabledFlags1) {
445            mDisabledFlags1 = masked;
446            if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
447        }
448    }
449
450    // ----- Internal stuffz -----
451
452    private void refreshLayout(int layoutDirection) {
453        if (mNavigationBarView != null) {
454            mNavigationBarView.setLayoutDirection(layoutDirection);
455        }
456    }
457
458    private boolean shouldDisableNavbarGestures() {
459        return !mStatusBar.isDeviceProvisioned()
460                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0
461                || mOverviewProxyService.getProxy() != null;
462    }
463
464    private void repositionNavigationBar() {
465        if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
466
467        prepareNavigationBarView();
468
469        mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
470                ((View) mNavigationBarView.getParent()).getLayoutParams());
471    }
472
473    private void notifyNavigationBarScreenOn() {
474        mNavigationBarView.notifyScreenOn();
475    }
476
477    private void prepareNavigationBarView() {
478        mNavigationBarView.reorient();
479
480        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
481        recentsButton.setOnClickListener(this::onRecentsClick);
482        recentsButton.setOnTouchListener(this::onRecentsTouch);
483        recentsButton.setLongClickable(true);
484        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
485
486        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
487        backButton.setLongClickable(true);
488        backButton.setOnLongClickListener(this::onLongPressBackRecents);
489
490        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
491        homeButton.setOnTouchListener(this::onHomeTouch);
492        homeButton.setOnLongClickListener(this::onHomeLongClick);
493
494        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
495        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
496        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
497        updateAccessibilityServicesState(mAccessibilityManager);
498
499        ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
500        rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
501        rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover);
502    }
503
504    private boolean onHomeTouch(View v, MotionEvent event) {
505        if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
506            return true;
507        }
508        // If an incoming call is ringing, HOME is totally disabled.
509        // (The user is already on the InCallUI at this point,
510        // and his ONLY options are to answer or reject the call.)
511        switch (event.getAction()) {
512            case MotionEvent.ACTION_DOWN:
513                mHomeBlockedThisTouch = false;
514                TelecomManager telecomManager =
515                        getContext().getSystemService(TelecomManager.class);
516                if (telecomManager != null && telecomManager.isRinging()) {
517                    if (mStatusBar.isKeyguardShowing()) {
518                        Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
519                                "No heads up");
520                        mHomeBlockedThisTouch = true;
521                        return true;
522                    }
523                }
524                break;
525            case MotionEvent.ACTION_UP:
526            case MotionEvent.ACTION_CANCEL:
527                mStatusBar.awakenDreams();
528                break;
529        }
530        return false;
531    }
532
533    private void onVerticalChanged(boolean isVertical) {
534        mStatusBar.setQsScrimEnabled(!isVertical);
535    }
536
537    private boolean onNavigationTouch(View v, MotionEvent event) {
538        mStatusBar.checkUserAutohide(event);
539        return false;
540    }
541
542    @VisibleForTesting
543    boolean onHomeLongClick(View v) {
544        if (shouldDisableNavbarGestures()) {
545            return false;
546        }
547        mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
548        mAssistManager.startAssist(new Bundle() /* args */);
549        mStatusBar.awakenDreams();
550
551        if (mNavigationBarView != null) {
552            mNavigationBarView.abortCurrentGesture();
553        }
554        return true;
555    }
556
557    // additional optimization when we have software system buttons - start loading the recent
558    // tasks on touch down
559    private boolean onRecentsTouch(View v, MotionEvent event) {
560        int action = event.getAction() & MotionEvent.ACTION_MASK;
561        if (action == MotionEvent.ACTION_DOWN) {
562            mCommandQueue.preloadRecentApps();
563        } else if (action == MotionEvent.ACTION_CANCEL) {
564            mCommandQueue.cancelPreloadRecentApps();
565        } else if (action == MotionEvent.ACTION_UP) {
566            if (!v.isPressed()) {
567                mCommandQueue.cancelPreloadRecentApps();
568            }
569        }
570        return false;
571    }
572
573    private void onRecentsClick(View v) {
574        if (LatencyTracker.isEnabled(getContext())) {
575            LatencyTracker.getInstance(getContext()).onActionStart(
576                    LatencyTracker.ACTION_TOGGLE_RECENTS);
577        }
578        mStatusBar.awakenDreams();
579        mCommandQueue.toggleRecentApps();
580    }
581
582    /**
583     * This handles long-press of both back and recents.  They are
584     * handled together to capture them both being long-pressed
585     * at the same time to exit screen pinning (lock task).
586     *
587     * When accessibility mode is on, only a long-press from recents
588     * is required to exit.
589     *
590     * In all other circumstances we try to pass through long-press events
591     * for Back, so that apps can still use it.  Which can be from two things.
592     * 1) Not currently in screen pinning (lock task).
593     * 2) Back is long-pressed without recents.
594     */
595    private boolean onLongPressBackRecents(View v) {
596        try {
597            boolean sendBackLongPress = false;
598            IActivityManager activityManager = ActivityManagerNative.getDefault();
599            boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
600            boolean inLockTaskMode = activityManager.isInLockTaskMode();
601            if (inLockTaskMode && !touchExplorationEnabled) {
602                long time = System.currentTimeMillis();
603                // If we recently long-pressed the other button then they were
604                // long-pressed 'together'
605                if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
606                    activityManager.stopSystemLockTaskMode();
607                    // When exiting refresh disabled flags.
608                    mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
609                    return true;
610                } else if ((v.getId() == R.id.back)
611                        && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) {
612                    // If we aren't pressing recents right now then they presses
613                    // won't be together, so send the standard long-press action.
614                    sendBackLongPress = true;
615                }
616                mLastLockToAppLongPress = time;
617            } else {
618                // If this is back still need to handle sending the long-press event.
619                if (v.getId() == R.id.back) {
620                    sendBackLongPress = true;
621                } else if (touchExplorationEnabled && inLockTaskMode) {
622                    // When in accessibility mode a long press that is recents (not back)
623                    // should stop lock task.
624                    activityManager.stopSystemLockTaskMode();
625                    // When exiting refresh disabled flags.
626                    mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
627                    return true;
628                } else if (v.getId() == R.id.recent_apps) {
629                    return onLongPressRecents();
630                }
631            }
632            if (sendBackLongPress) {
633                KeyButtonView keyButtonView = (KeyButtonView) v;
634                keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
635                keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
636                return true;
637            }
638        } catch (RemoteException e) {
639            Log.d(TAG, "Unable to reach activity manager", e);
640        }
641        return false;
642    }
643
644    private boolean onLongPressRecents() {
645        if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext())
646                || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
647                || Recents.getConfiguration().isLowRamDevice) {
648            return false;
649        }
650
651        return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
652                MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
653    }
654
655    private void onAccessibilityClick(View v) {
656        mAccessibilityManager.notifyAccessibilityButtonClicked();
657    }
658
659    private boolean onAccessibilityLongClick(View v) {
660        Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
661        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
662        v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
663        return true;
664    }
665
666    private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
667        int requestingServices = 0;
668        try {
669            if (Settings.Secure.getIntForUser(mContentResolver,
670                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
671                    UserHandle.USER_CURRENT) == 1) {
672                requestingServices++;
673            }
674        } catch (Settings.SettingNotFoundException e) {
675        }
676
677        boolean feedbackEnabled = false;
678        // AccessibilityManagerService resolves services for the current user since the local
679        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
680        final List<AccessibilityServiceInfo> services =
681                accessibilityManager.getEnabledAccessibilityServiceList(
682                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
683        for (int i = services.size() - 1; i >= 0; --i) {
684            AccessibilityServiceInfo info = services.get(i);
685            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
686                requestingServices++;
687            }
688
689            if (info.feedbackType != 0 && info.feedbackType !=
690                    AccessibilityServiceInfo.FEEDBACK_GENERIC) {
691                feedbackEnabled = true;
692            }
693        }
694
695        mAccessibilityFeedbackEnabled = feedbackEnabled;
696
697        final boolean showAccessibilityButton = requestingServices >= 1;
698        final boolean targetSelection = requestingServices >= 2;
699        mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
700    }
701
702    private void onRotateSuggestionClick(View v) {
703        mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
704        mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
705    }
706
707    private boolean onRotateSuggestionHover(View v, MotionEvent event) {
708        final int action = event.getActionMasked();
709        mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
710                || (action == MotionEvent.ACTION_HOVER_MOVE);
711        rescheduleRotationTimeout(true);
712        return false; // Must return false so a11y hover events are dispatched correctly.
713    }
714
715    // ----- Methods that StatusBar talks to (should be minimized) -----
716
717    public void setLightBarController(LightBarController lightBarController) {
718        mLightBarController = lightBarController;
719        mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
720    }
721
722    public boolean isSemiTransparent() {
723        return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
724    }
725
726    public void disableAnimationsDuringHide(long delay) {
727        mNavigationBarView.setLayoutTransitionsEnabled(false);
728        mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
729                delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
730    }
731
732    public BarTransitions getBarTransitions() {
733        return mNavigationBarView.getBarTransitions();
734    }
735
736    public void checkNavBarModes() {
737        mStatusBar.checkBarMode(mNavigationBarMode,
738                mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
739    }
740
741    public void finishBarAnimations() {
742        mNavigationBarView.getBarTransitions().finishAnimations();
743    }
744
745    private final AccessibilityServicesStateChangeListener mAccessibilityListener =
746            this::updateAccessibilityServicesState;
747
748    private class MagnificationContentObserver extends ContentObserver {
749
750        public MagnificationContentObserver(Handler handler) {
751            super(handler);
752        }
753
754        @Override
755        public void onChange(boolean selfChange) {
756            NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
757        }
758    }
759
760    private final Stub mRotationWatcher = new Stub() {
761        @Override
762        public void onRotationChanged(final int rotation) throws RemoteException {
763            // We need this to be scheduled as early as possible to beat the redrawing of
764            // window in response to the orientation change.
765            Handler h = getView().getHandler();
766            Message msg = Message.obtain(h, () -> {
767
768                // If the screen rotation changes while locked, potentially update lock to flow with
769                // new screen rotation and hide any showing suggestions.
770                if (mRotationLockController.isRotationLocked()) {
771                    if (shouldOverrideUserLockPrefs(rotation)) {
772                        mRotationLockController.setRotationLockedAtAngle(true, rotation);
773                    }
774                    safeSetRotationButtonState(false, true);
775                }
776
777                if (mNavigationBarView != null
778                        && mNavigationBarView.needsReorient(rotation)) {
779                    repositionNavigationBar();
780                }
781            });
782            msg.setAsynchronous(true);
783            h.sendMessageAtFrontOfQueue(msg);
784        }
785
786        private boolean shouldOverrideUserLockPrefs(final int rotation) {
787            // Only override user prefs when returning to portrait.
788            // Don't let apps that force landscape or 180 alter user lock.
789            return rotation == Surface.ROTATION_0;
790        }
791    };
792
793    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
794        @Override
795        public void onReceive(Context context, Intent intent) {
796            String action = intent.getAction();
797            if (Intent.ACTION_SCREEN_OFF.equals(action)
798                    || Intent.ACTION_SCREEN_ON.equals(action)) {
799                notifyNavigationBarScreenOn();
800            }
801        }
802    };
803
804    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
805        // Invalidate any rotation suggestion on task change or activity orientation change
806        // Note: all callbacks happen on main thread
807
808        @Override
809        public void onTaskStackChanged() {
810            safeSetRotationButtonState(false);
811        }
812
813        @Override
814        public void onTaskRemoved(int taskId) {
815            safeSetRotationButtonState(false);
816        }
817
818        @Override
819        public void onTaskMovedToFront(int taskId) {
820            safeSetRotationButtonState(false);
821        }
822
823        @Override
824        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
825            safeSetRotationButtonState(false);
826        }
827    }
828
829    public static View create(Context context, FragmentListener listener) {
830        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
831                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
832                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
833                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
834                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
835                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
836                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
837                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
838                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
839                PixelFormat.TRANSLUCENT);
840        lp.token = new Binder();
841        lp.setTitle("NavigationBar");
842        lp.windowAnimations = 0;
843
844        View navigationBarView = LayoutInflater.from(context).inflate(
845                R.layout.navigation_bar_window, null);
846
847        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
848        if (navigationBarView == null) return null;
849
850        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
851        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
852        NavigationBarFragment fragment = new NavigationBarFragment();
853        fragmentHost.getFragmentManager().beginTransaction()
854                .replace(R.id.navigation_bar_frame, fragment, TAG)
855                .commit();
856        fragmentHost.addTagListener(TAG, listener);
857        return navigationBarView;
858    }
859}
860