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