NavigationBarFragment.java revision e07773175a55807e68aba6cd8a69b2401b748883
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, boolean isValid) {
339        // This method will be called on rotation suggestion changes even if the proposed rotation
340        // is not valid for the top app. Use invalid rotation choices as a signal to remove the
341        // rotate button if shown.
342
343        if (!isValid) {
344            setRotateSuggestionButtonState(false);
345            return;
346        }
347
348        Handler h = getView().getHandler();
349        if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
350            // Use this as a signal to remove any current suggestions
351            h.removeCallbacks(mRemoveRotationProposal);
352            setRotateSuggestionButtonState(false);
353        } else {
354            mLastRotationSuggestion = rotation; // Remember rotation for click
355            setRotateSuggestionButtonState(true);
356            h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
357            h.postDelayed(mRemoveRotationProposal,
358                    ROTATE_SUGGESTION_TIMEOUT_MS); // Schedule timeout
359        }
360    }
361
362    public void setRotateSuggestionButtonState(final boolean visible) {
363        setRotateSuggestionButtonState(visible, false);
364    }
365
366    public void setRotateSuggestionButtonState(final boolean visible, final boolean skipAnim) {
367        ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
368        boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE;
369
370        // Rerun a show animation to indicate change but don't rerun a hide animation
371        if (!visible && !currentlyVisible) return;
372
373        View currentView = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
374        if (currentView == null) return;
375
376        KeyButtonDrawable kbd = mNavigationBarView.getRotateSuggestionButton().getImageDrawable();
377        if (kbd == null) return;
378
379        AnimatedVectorDrawable animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
380        if (visible) { // Appear and change
381            rotBtn.setVisibility(View.VISIBLE);
382
383            if (skipAnim) {
384                currentView.setAlpha(1f);
385                return;
386            }
387
388            // Start a new animation if running
389            if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
390            if (mRotateHideAnimator != null) mRotateHideAnimator.pause();
391
392            ObjectAnimator appearFade = ObjectAnimator.ofFloat(currentView, "alpha",
393                    0f, 1f);
394            appearFade.setDuration(100);
395            appearFade.setInterpolator(Interpolators.LINEAR);
396            mRotateShowAnimator = appearFade;
397            appearFade.start();
398
399            // Run the rotate icon's animation
400            animIcon.reset();
401            animIcon.start();
402        } else { // Hide
403
404            if (skipAnim) {
405                rotBtn.setVisibility(View.INVISIBLE);
406                return;
407            }
408
409            // Don't start any new hide animations if one is running
410            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
411            // Pause any active show animations but don't reset the AVD to avoid jumps
412            if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
413
414            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
415                    0f);
416            fadeOut.setDuration(100);
417            fadeOut.setInterpolator(Interpolators.LINEAR);
418            fadeOut.addListener(new AnimatorListenerAdapter() {
419                @Override
420                public void onAnimationEnd(Animator animation) {
421                    rotBtn.setVisibility(View.INVISIBLE);
422                }
423            });
424
425            mRotateHideAnimator = fadeOut;
426            fadeOut.start();
427        }
428    }
429
430    // Injected from StatusBar at creation.
431    public void setCurrentSysuiVisibility(int systemUiVisibility) {
432        mSystemUiVisibility = systemUiVisibility;
433        mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
434                View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
435                View.NAVIGATION_BAR_TRANSPARENT);
436        checkNavBarModes();
437        mStatusBar.touchAutoHide();
438        mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
439                true /* nbModeChanged */, mNavigationBarMode);
440    }
441
442    @Override
443    public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
444            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
445        final int oldVal = mSystemUiVisibility;
446        final int newVal = (oldVal & ~mask) | (vis & mask);
447        final int diff = newVal ^ oldVal;
448        boolean nbModeChanged = false;
449        if (diff != 0) {
450            mSystemUiVisibility = newVal;
451
452            // update navigation bar mode
453            final int nbMode = getView() == null
454                    ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
455                    View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
456                    View.NAVIGATION_BAR_TRANSPARENT);
457            nbModeChanged = nbMode != -1;
458            if (nbModeChanged) {
459                if (mNavigationBarMode != nbMode) {
460                    mNavigationBarMode = nbMode;
461                    checkNavBarModes();
462                }
463                mStatusBar.touchAutoHide();
464            }
465        }
466
467        mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged,
468                mNavigationBarMode);
469    }
470
471    @Override
472    public void disable(int state1, int state2, boolean animate) {
473        // All navigation bar flags are in state1.
474        int masked = state1 & (StatusBarManager.DISABLE_HOME
475                | StatusBarManager.DISABLE_RECENT
476                | StatusBarManager.DISABLE_BACK
477                | StatusBarManager.DISABLE_SEARCH);
478        if (masked != mDisabledFlags1) {
479            mDisabledFlags1 = masked;
480            if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
481        }
482    }
483
484    // ----- Internal stuffz -----
485
486    private void refreshLayout(int layoutDirection) {
487        if (mNavigationBarView != null) {
488            mNavigationBarView.setLayoutDirection(layoutDirection);
489        }
490    }
491
492    private boolean shouldDisableNavbarGestures() {
493        return !mStatusBar.isDeviceProvisioned()
494                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0
495                || mOverviewProxyService.getProxy() != null;
496    }
497
498    private void repositionNavigationBar() {
499        if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
500
501        prepareNavigationBarView();
502
503        mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
504                ((View) mNavigationBarView.getParent()).getLayoutParams());
505    }
506
507    private void notifyNavigationBarScreenOn() {
508        mNavigationBarView.notifyScreenOn();
509    }
510
511    private void prepareNavigationBarView() {
512        mNavigationBarView.reorient();
513
514        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
515        recentsButton.setOnClickListener(this::onRecentsClick);
516        recentsButton.setOnTouchListener(this::onRecentsTouch);
517        recentsButton.setLongClickable(true);
518        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
519
520        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
521        backButton.setLongClickable(true);
522        backButton.setOnLongClickListener(this::onLongPressBackRecents);
523
524        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
525        homeButton.setOnTouchListener(this::onHomeTouch);
526        homeButton.setOnLongClickListener(this::onHomeLongClick);
527
528        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
529        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
530        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
531        updateAccessibilityServicesState(mAccessibilityManager);
532
533        ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
534        rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
535    }
536
537    private boolean onHomeTouch(View v, MotionEvent event) {
538        if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
539            return true;
540        }
541        // If an incoming call is ringing, HOME is totally disabled.
542        // (The user is already on the InCallUI at this point,
543        // and his ONLY options are to answer or reject the call.)
544        switch (event.getAction()) {
545            case MotionEvent.ACTION_DOWN:
546                mHomeBlockedThisTouch = false;
547                TelecomManager telecomManager =
548                        getContext().getSystemService(TelecomManager.class);
549                if (telecomManager != null && telecomManager.isRinging()) {
550                    if (mStatusBar.isKeyguardShowing()) {
551                        Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
552                                "No heads up");
553                        mHomeBlockedThisTouch = true;
554                        return true;
555                    }
556                }
557                break;
558            case MotionEvent.ACTION_UP:
559            case MotionEvent.ACTION_CANCEL:
560                mStatusBar.awakenDreams();
561                break;
562        }
563        return false;
564    }
565
566    private void onVerticalChanged(boolean isVertical) {
567        mStatusBar.setQsScrimEnabled(!isVertical);
568    }
569
570    private boolean onNavigationTouch(View v, MotionEvent event) {
571        mStatusBar.checkUserAutohide(event);
572        return false;
573    }
574
575    @VisibleForTesting
576    boolean onHomeLongClick(View v) {
577        if (shouldDisableNavbarGestures()) {
578            return false;
579        }
580        MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
581        mAssistManager.startAssist(new Bundle() /* args */);
582        mStatusBar.awakenDreams();
583
584        if (mNavigationBarView != null) {
585            mNavigationBarView.abortCurrentGesture();
586        }
587        return true;
588    }
589
590    // additional optimization when we have software system buttons - start loading the recent
591    // tasks on touch down
592    private boolean onRecentsTouch(View v, MotionEvent event) {
593        int action = event.getAction() & MotionEvent.ACTION_MASK;
594        if (action == MotionEvent.ACTION_DOWN) {
595            mCommandQueue.preloadRecentApps();
596        } else if (action == MotionEvent.ACTION_CANCEL) {
597            mCommandQueue.cancelPreloadRecentApps();
598        } else if (action == MotionEvent.ACTION_UP) {
599            if (!v.isPressed()) {
600                mCommandQueue.cancelPreloadRecentApps();
601            }
602        }
603        return false;
604    }
605
606    private void onRecentsClick(View v) {
607        if (LatencyTracker.isEnabled(getContext())) {
608            LatencyTracker.getInstance(getContext()).onActionStart(
609                    LatencyTracker.ACTION_TOGGLE_RECENTS);
610        }
611        mStatusBar.awakenDreams();
612        mCommandQueue.toggleRecentApps();
613    }
614
615    /**
616     * This handles long-press of both back and recents.  They are
617     * handled together to capture them both being long-pressed
618     * at the same time to exit screen pinning (lock task).
619     *
620     * When accessibility mode is on, only a long-press from recents
621     * is required to exit.
622     *
623     * In all other circumstances we try to pass through long-press events
624     * for Back, so that apps can still use it.  Which can be from two things.
625     * 1) Not currently in screen pinning (lock task).
626     * 2) Back is long-pressed without recents.
627     */
628    private boolean onLongPressBackRecents(View v) {
629        try {
630            boolean sendBackLongPress = false;
631            IActivityManager activityManager = ActivityManagerNative.getDefault();
632            boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
633            boolean inLockTaskMode = activityManager.isInLockTaskMode();
634            if (inLockTaskMode && !touchExplorationEnabled) {
635                long time = System.currentTimeMillis();
636                // If we recently long-pressed the other button then they were
637                // long-pressed 'together'
638                if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
639                    activityManager.stopSystemLockTaskMode();
640                    // When exiting refresh disabled flags.
641                    mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
642                    return true;
643                } else if ((v.getId() == R.id.back)
644                        && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) {
645                    // If we aren't pressing recents right now then they presses
646                    // won't be together, so send the standard long-press action.
647                    sendBackLongPress = true;
648                }
649                mLastLockToAppLongPress = time;
650            } else {
651                // If this is back still need to handle sending the long-press event.
652                if (v.getId() == R.id.back) {
653                    sendBackLongPress = true;
654                } else if (touchExplorationEnabled && inLockTaskMode) {
655                    // When in accessibility mode a long press that is recents (not back)
656                    // should stop lock task.
657                    activityManager.stopSystemLockTaskMode();
658                    // When exiting refresh disabled flags.
659                    mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
660                    return true;
661                } else if (v.getId() == R.id.recent_apps) {
662                    return onLongPressRecents();
663                }
664            }
665            if (sendBackLongPress) {
666                KeyButtonView keyButtonView = (KeyButtonView) v;
667                keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
668                keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
669                return true;
670            }
671        } catch (RemoteException e) {
672            Log.d(TAG, "Unable to reach activity manager", e);
673        }
674        return false;
675    }
676
677    private boolean onLongPressRecents() {
678        if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext())
679                || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
680                || Recents.getConfiguration().isLowRamDevice) {
681            return false;
682        }
683
684        return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
685                MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
686    }
687
688    private void onAccessibilityClick(View v) {
689        mAccessibilityManager.notifyAccessibilityButtonClicked();
690    }
691
692    private boolean onAccessibilityLongClick(View v) {
693        Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
694        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
695        v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
696        return true;
697    }
698
699    private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
700        int requestingServices = 0;
701        try {
702            if (Settings.Secure.getIntForUser(mContentResolver,
703                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
704                    UserHandle.USER_CURRENT) == 1) {
705                requestingServices++;
706            }
707        } catch (Settings.SettingNotFoundException e) {
708        }
709
710        // AccessibilityManagerService resolves services for the current user since the local
711        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
712        final List<AccessibilityServiceInfo> services =
713                accessibilityManager.getEnabledAccessibilityServiceList(
714                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
715        for (int i = services.size() - 1; i >= 0; --i) {
716            AccessibilityServiceInfo info = services.get(i);
717            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
718                requestingServices++;
719            }
720        }
721
722        final boolean showAccessibilityButton = requestingServices >= 1;
723        final boolean targetSelection = requestingServices >= 2;
724        mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
725    }
726
727    private void onRotateSuggestionClick(View v) {
728        mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
729    }
730
731    // ----- Methods that StatusBar talks to (should be minimized) -----
732
733    public void setLightBarController(LightBarController lightBarController) {
734        mLightBarController = lightBarController;
735        mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
736    }
737
738    public boolean isSemiTransparent() {
739        return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
740    }
741
742    public void disableAnimationsDuringHide(long delay) {
743        mNavigationBarView.setLayoutTransitionsEnabled(false);
744        mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
745                delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
746    }
747
748    public BarTransitions getBarTransitions() {
749        return mNavigationBarView.getBarTransitions();
750    }
751
752    public void checkNavBarModes() {
753        mStatusBar.checkBarMode(mNavigationBarMode,
754                mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
755    }
756
757    public void finishBarAnimations() {
758        mNavigationBarView.getBarTransitions().finishAnimations();
759    }
760
761    private final AccessibilityServicesStateChangeListener mAccessibilityListener =
762            this::updateAccessibilityServicesState;
763
764    private class MagnificationContentObserver extends ContentObserver {
765
766        public MagnificationContentObserver(Handler handler) {
767            super(handler);
768        }
769
770        @Override
771        public void onChange(boolean selfChange) {
772            NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
773        }
774    }
775
776    private final Stub mRotationWatcher = new Stub() {
777        @Override
778        public void onRotationChanged(int rotation) throws RemoteException {
779            // If the screen rotation changes while locked, update lock rotation to flow with
780            // new screen rotation and hide any showing suggestions.
781            if (mRotationLockController.isRotationLocked()) {
782                mRotationLockController.setRotationLockedAtAngle(true, rotation);
783                setRotateSuggestionButtonState(false, true);
784            }
785
786            // We need this to be scheduled as early as possible to beat the redrawing of
787            // window in response to the orientation change.
788            Handler h = getView().getHandler();
789            Message msg = Message.obtain(h, () -> {
790                if (mNavigationBarView != null
791                        && mNavigationBarView.needsReorient(rotation)) {
792                    repositionNavigationBar();
793                }
794            });
795            msg.setAsynchronous(true);
796            h.sendMessageAtFrontOfQueue(msg);
797        }
798    };
799
800    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
801        @Override
802        public void onReceive(Context context, Intent intent) {
803            String action = intent.getAction();
804            if (Intent.ACTION_SCREEN_OFF.equals(action)
805                    || Intent.ACTION_SCREEN_ON.equals(action)) {
806                notifyNavigationBarScreenOn();
807            }
808        }
809    };
810
811    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
812        // Invalidate any rotation suggestion on task change or activity orientation change
813        // Note: all callbacks happen on main thread
814
815        @Override
816        public void onTaskStackChanged() {
817            setRotateSuggestionButtonState(false);
818        }
819
820        @Override
821        public void onTaskRemoved(int taskId) {
822            setRotateSuggestionButtonState(false);
823        }
824
825        @Override
826        public void onTaskMovedToFront(int taskId) {
827            setRotateSuggestionButtonState(false);
828        }
829
830        @Override
831        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
832            setRotateSuggestionButtonState(false);
833        }
834    }
835
836    public static View create(Context context, FragmentListener listener) {
837        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
838                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
839                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
840                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
841                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
842                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
843                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
844                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
845                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
846                PixelFormat.TRANSLUCENT);
847        lp.token = new Binder();
848        lp.setTitle("NavigationBar");
849        lp.windowAnimations = 0;
850
851        View navigationBarView = LayoutInflater.from(context).inflate(
852                R.layout.navigation_bar_window, null);
853
854        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
855        if (navigationBarView == null) return null;
856
857        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
858        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
859        NavigationBarFragment fragment = new NavigationBarFragment();
860        fragmentHost.getFragmentManager().beginTransaction()
861                .replace(R.id.navigation_bar_frame, fragment, TAG)
862                .commit();
863        fragmentHost.addTagListener(TAG, listener);
864        return navigationBarView;
865    }
866}
867