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