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 
15 package com.android.systemui.statusbar.phone;
16 
17 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
18 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
19 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
20 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
21 import static android.app.StatusBarManager.WindowType;
22 import static android.app.StatusBarManager.WindowVisibleState;
23 import static android.app.StatusBarManager.windowStateToString;
24 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
25 
26 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
30 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
31 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
32 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
33 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
34 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
35 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
36 import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
37 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
38 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
39 
40 import android.accessibilityservice.AccessibilityServiceInfo;
41 import android.annotation.IdRes;
42 import android.annotation.Nullable;
43 import android.app.ActivityManager;
44 import android.app.ActivityTaskManager;
45 import android.app.IActivityTaskManager;
46 import android.app.StatusBarManager;
47 import android.content.BroadcastReceiver;
48 import android.content.ContentResolver;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.IntentFilter;
52 import android.content.res.Configuration;
53 import android.database.ContentObserver;
54 import android.graphics.PixelFormat;
55 import android.graphics.Rect;
56 import android.inputmethodservice.InputMethodService;
57 import android.net.Uri;
58 import android.os.Binder;
59 import android.os.Bundle;
60 import android.os.Handler;
61 import android.os.IBinder;
62 import android.os.Looper;
63 import android.os.RemoteException;
64 import android.os.UserHandle;
65 import android.provider.Settings;
66 import android.telecom.TelecomManager;
67 import android.text.TextUtils;
68 import android.util.Log;
69 import android.view.Display;
70 import android.view.KeyEvent;
71 import android.view.LayoutInflater;
72 import android.view.MotionEvent;
73 import android.view.Surface;
74 import android.view.View;
75 import android.view.ViewGroup;
76 import android.view.WindowManager;
77 import android.view.WindowManager.LayoutParams;
78 import android.view.accessibility.AccessibilityEvent;
79 import android.view.accessibility.AccessibilityManager;
80 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
81 
82 import androidx.annotation.VisibleForTesting;
83 
84 import com.android.internal.logging.MetricsLogger;
85 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
86 import com.android.internal.util.LatencyTracker;
87 import com.android.systemui.Dependency;
88 import com.android.systemui.R;
89 import com.android.systemui.ScreenDecorations;
90 import com.android.systemui.SysUiServiceProvider;
91 import com.android.systemui.assist.AssistManager;
92 import com.android.systemui.fragments.FragmentHostManager;
93 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
94 import com.android.systemui.plugins.statusbar.StatusBarStateController;
95 import com.android.systemui.recents.OverviewProxyService;
96 import com.android.systemui.recents.Recents;
97 import com.android.systemui.shared.system.ActivityManagerWrapper;
98 import com.android.systemui.shared.system.QuickStepContract;
99 import com.android.systemui.stackdivider.Divider;
100 import com.android.systemui.statusbar.CommandQueue;
101 import com.android.systemui.statusbar.CommandQueue.Callbacks;
102 import com.android.systemui.statusbar.StatusBarState;
103 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
104 import com.android.systemui.statusbar.phone.ContextualButton.ContextButtonListener;
105 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
106 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
107 import com.android.systemui.statusbar.policy.KeyButtonView;
108 import com.android.systemui.util.LifecycleFragment;
109 
110 import java.io.FileDescriptor;
111 import java.io.PrintWriter;
112 import java.util.List;
113 import java.util.Locale;
114 import java.util.function.Consumer;
115 
116 import javax.inject.Inject;
117 
118 /**
119  * Fragment containing the NavigationBarFragment. Contains logic for what happens
120  * on clicks and view states of the nav bar.
121  */
122 public class NavigationBarFragment extends LifecycleFragment implements Callbacks,
123         NavigationModeController.ModeChangedListener {
124 
125     public static final String TAG = "NavigationBar";
126     private static final boolean DEBUG = false;
127     private static final String EXTRA_DISABLE_STATE = "disabled_state";
128     private static final String EXTRA_DISABLE2_STATE = "disabled2_state";
129     private static final String EXTRA_SYSTEM_UI_VISIBILITY = "system_ui_visibility";
130 
131     /** Allow some time inbetween the long press for back and recents. */
132     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
133     private static final long AUTODIM_TIMEOUT_MS = 2250;
134 
135     private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
136     protected final AssistManager mAssistManager;
137     private final MetricsLogger mMetricsLogger;
138     private final DeviceProvisionedController mDeviceProvisionedController;
139     private final StatusBarStateController mStatusBarStateController;
140 
141     protected NavigationBarView mNavigationBarView = null;
142 
143     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
144 
145     private int mNavigationIconHints = 0;
146     private @TransitionMode int mNavigationBarMode;
147     private AccessibilityManager mAccessibilityManager;
148     private MagnificationContentObserver mMagnificationObserver;
149     private ContentResolver mContentResolver;
150     private boolean mAssistantAvailable;
151 
152     private int mDisabledFlags1;
153     private int mDisabledFlags2;
154     private StatusBar mStatusBar;
155     private Recents mRecents;
156     private Divider mDivider;
157     private WindowManager mWindowManager;
158     private CommandQueue mCommandQueue;
159     private long mLastLockToAppLongPress;
160 
161     private Locale mLocale;
162     private int mLayoutDirection;
163 
164     private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
165     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
166     private LightBarController mLightBarController;
167     private AutoHideController mAutoHideController;
168 
169     private OverviewProxyService mOverviewProxyService;
170 
171     @VisibleForTesting
172     public int mDisplayId;
173     private boolean mIsOnDefaultDisplay;
174     public boolean mHomeBlockedThisTouch;
175     private ScreenDecorations mScreenDecorations;
176 
177     private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER);
178 
179     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
180         @Override
181         public void onConnectionChanged(boolean isConnected) {
182             mNavigationBarView.updateStates();
183             updateScreenPinningGestures();
184 
185             // Send the assistant availability upon connection
186             if (isConnected) {
187                 sendAssistantAvailability(mAssistantAvailable);
188             }
189         }
190 
191         @Override
192         public void onQuickStepStarted() {
193             // Use navbar dragging as a signal to hide the rotate button
194             mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
195 
196             // Hide the notifications panel when quick step starts
197             mStatusBar.collapsePanel(true /* animate */);
198         }
199 
200         @Override
201         public void startAssistant(Bundle bundle) {
202             mAssistManager.startAssist(bundle);
203         }
204 
205         @Override
206         public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
207             ButtonDispatcher buttonDispatcher = null;
208             if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
209                 buttonDispatcher = mNavigationBarView.getBackButton();
210             } else if (QuickStepContract.isGesturalMode(mNavBarMode)) {
211                 buttonDispatcher = mNavigationBarView.getHomeHandle();
212             }
213             if (buttonDispatcher != null) {
214                 buttonDispatcher.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE);
215                 buttonDispatcher.setAlpha(alpha, animate);
216             }
217         }
218     };
219 
220     private final ContextButtonListener mRotationButtonListener = (button, visible) -> {
221         if (visible) {
222             // If the button will actually become visible and the navbar is about to hide,
223             // tell the statusbar to keep it around for longer
224             mAutoHideController.touchAutoHide();
225         }
226     };
227 
228     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
229 
230     private final ContentObserver mAssistContentObserver = new ContentObserver(
231             new Handler(Looper.getMainLooper())) {
232         @Override
233         public void onChange(boolean selfChange, Uri uri) {
234             boolean available = mAssistManager
235                     .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
236             if (mAssistantAvailable != available) {
237                 sendAssistantAvailability(available);
238                 mAssistantAvailable = available;
239             }
240         }
241     };
242 
243     @Inject
NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, AssistManager assistManager, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, StatusBarStateController statusBarStateController)244     public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
245             DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
246             AssistManager assistManager, OverviewProxyService overviewProxyService,
247             NavigationModeController navigationModeController,
248             StatusBarStateController statusBarStateController) {
249         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
250         mDeviceProvisionedController = deviceProvisionedController;
251         mStatusBarStateController = statusBarStateController;
252         mMetricsLogger = metricsLogger;
253         mAssistManager = assistManager;
254         mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
255         mOverviewProxyService = overviewProxyService;
256         mNavBarMode = navigationModeController.addListener(this);
257     }
258 
259     // ----- Fragment Lifecycle Callbacks -----
260 
261     @Override
onCreate(@ullable Bundle savedInstanceState)262     public void onCreate(@Nullable Bundle savedInstanceState) {
263         super.onCreate(savedInstanceState);
264         mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
265         mCommandQueue.observe(getLifecycle(), this);
266         mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
267         mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
268         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
269         mWindowManager = getContext().getSystemService(WindowManager.class);
270         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
271         mContentResolver = getContext().getContentResolver();
272         mMagnificationObserver = new MagnificationContentObserver(
273                 getContext().getMainThreadHandler());
274         mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
275                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
276                 mMagnificationObserver, UserHandle.USER_ALL);
277         mContentResolver.registerContentObserver(
278                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
279                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
280 
281         if (savedInstanceState != null) {
282             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
283             mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
284             mSystemUiVisibility = savedInstanceState.getInt(EXTRA_SYSTEM_UI_VISIBILITY, 0);
285         }
286         mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
287 
288         // Respect the latest disabled-flags.
289         mCommandQueue.recomputeDisableFlags(mDisplayId, false);
290     }
291 
292     @Override
onDestroy()293     public void onDestroy() {
294         super.onDestroy();
295         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
296         mContentResolver.unregisterContentObserver(mMagnificationObserver);
297         mContentResolver.unregisterContentObserver(mAssistContentObserver);
298     }
299 
300     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)301     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
302             Bundle savedInstanceState) {
303         return inflater.inflate(R.layout.navigation_bar, container, false);
304     }
305 
306     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)307     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
308         super.onViewCreated(view, savedInstanceState);
309         mNavigationBarView = (NavigationBarView) view;
310         final Display display = view.getDisplay();
311         // It may not have display when running unit test.
312         if (display != null) {
313             mDisplayId = display.getDisplayId();
314             mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
315         }
316 
317         mNavigationBarView.setComponents(mStatusBar.getPanel(), mAssistManager);
318         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
319         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
320         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
321         if (savedInstanceState != null) {
322             mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
323         }
324         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
325         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
326 
327         prepareNavigationBarView();
328         checkNavBarModes();
329 
330         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
331         filter.addAction(Intent.ACTION_SCREEN_ON);
332         filter.addAction(Intent.ACTION_USER_SWITCHED);
333         getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
334         notifyNavigationBarScreenOn();
335 
336         mOverviewProxyService.addCallback(mOverviewProxyListener);
337         updateSystemUiStateFlags(-1);
338 
339         // Currently there is no accelerometer sensor on non-default display.
340         if (mIsOnDefaultDisplay) {
341             mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
342 
343             final RotationButtonController rotationButtonController =
344                     mNavigationBarView.getRotationButtonController();
345             rotationButtonController.addRotationCallback(mRotationWatcher);
346 
347             // Reset user rotation pref to match that of the WindowManager if starting in locked
348             // mode. This will automatically happen when switching from auto-rotate to locked mode.
349             if (display != null && rotationButtonController.isRotationLocked()) {
350                 rotationButtonController.setRotationLockedAtAngle(display.getRotation());
351             }
352         } else {
353             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
354         }
355         setDisabled2Flags(mDisabledFlags2);
356 
357         mScreenDecorations = SysUiServiceProvider.getComponent(getContext(),
358                 ScreenDecorations.class);
359         getBarTransitions().addDarkIntensityListener(mScreenDecorations);
360     }
361 
362     @Override
onDestroyView()363     public void onDestroyView() {
364         super.onDestroyView();
365         if (mNavigationBarView != null) {
366             mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations);
367             mNavigationBarView.getBarTransitions().destroy();
368             mNavigationBarView.getLightTransitionsController().destroy(getContext());
369         }
370         mOverviewProxyService.removeCallback(mOverviewProxyListener);
371         getContext().unregisterReceiver(mBroadcastReceiver);
372     }
373 
374     @Override
onSaveInstanceState(Bundle outState)375     public void onSaveInstanceState(Bundle outState) {
376         super.onSaveInstanceState(outState);
377         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
378         outState.putInt(EXTRA_DISABLE2_STATE, mDisabledFlags2);
379         outState.putInt(EXTRA_SYSTEM_UI_VISIBILITY, mSystemUiVisibility);
380         if (mNavigationBarView != null) {
381             mNavigationBarView.getLightTransitionsController().saveState(outState);
382         }
383     }
384 
385     @Override
onConfigurationChanged(Configuration newConfig)386     public void onConfigurationChanged(Configuration newConfig) {
387         super.onConfigurationChanged(newConfig);
388         final Locale locale = getContext().getResources().getConfiguration().locale;
389         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
390         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
391             if (DEBUG) {
392                 Log.v(TAG, String.format(
393                         "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
394                         locale, ld));
395             }
396             mLocale = locale;
397             mLayoutDirection = ld;
398             refreshLayout(ld);
399         }
400         repositionNavigationBar();
401     }
402 
403     @Override
dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args)404     public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
405         if (mNavigationBarView != null) {
406             pw.print("  mNavigationBarWindowState=");
407             pw.println(windowStateToString(mNavigationBarWindowState));
408             pw.print("  mNavigationBarMode=");
409             pw.println(BarTransitions.modeToString(mNavigationBarMode));
410             dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
411         }
412 
413         pw.print("  mNavigationBarView=");
414         if (mNavigationBarView == null) {
415             pw.println("null");
416         } else {
417             mNavigationBarView.dump(fd, pw, args);
418         }
419     }
420 
421     // ----- CommandQueue Callbacks -----
422 
423     @Override
setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher)424     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
425             boolean showImeSwitcher) {
426         if (displayId != mDisplayId) {
427             return;
428         }
429         boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
430         int hints = mNavigationIconHints;
431         switch (backDisposition) {
432             case InputMethodService.BACK_DISPOSITION_DEFAULT:
433             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
434             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
435                 if (imeShown) {
436                     hints |= NAVIGATION_HINT_BACK_ALT;
437                 } else {
438                     hints &= ~NAVIGATION_HINT_BACK_ALT;
439                 }
440                 break;
441             case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
442                 hints &= ~NAVIGATION_HINT_BACK_ALT;
443                 break;
444         }
445         if (showImeSwitcher) {
446             hints |= NAVIGATION_HINT_IME_SHOWN;
447         } else {
448             hints &= ~NAVIGATION_HINT_IME_SHOWN;
449         }
450         if (hints == mNavigationIconHints) return;
451 
452         mNavigationIconHints = hints;
453 
454         if (mNavigationBarView != null) {
455             mNavigationBarView.setNavigationIconHints(hints);
456         }
457         checkBarModes();
458     }
459 
460     @Override
setWindowState( int displayId, @WindowType int window, @WindowVisibleState int state)461     public void setWindowState(
462             int displayId, @WindowType int window, @WindowVisibleState int state) {
463         if (displayId == mDisplayId
464                 && mNavigationBarView != null
465                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
466                 && mNavigationBarWindowState != state) {
467             mNavigationBarWindowState = state;
468             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
469 
470             updateSystemUiStateFlags(-1);
471             mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
472         }
473     }
474 
475     @Override
onRotationProposal(final int rotation, boolean isValid)476     public void onRotationProposal(final int rotation, boolean isValid) {
477         final int winRotation = mNavigationBarView.getDisplay().getRotation();
478         final boolean rotateSuggestionsDisabled = RotationButtonController
479                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
480         final RotationButtonController rotationButtonController =
481                 mNavigationBarView.getRotationButtonController();
482         final RotationButton rotationButton = rotationButtonController.getRotationButton();
483 
484         if (RotationContextButton.DEBUG_ROTATION) {
485             Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
486                     + ", winRotation=" + Surface.rotationToString(winRotation)
487                     + ", isValid=" + isValid + ", mNavBarWindowState="
488                     + StatusBarManager.windowStateToString(mNavigationBarWindowState)
489                     + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
490                     + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null"
491                     : rotationButton.isVisible()));
492         }
493 
494         // Respect the disabled flag, no need for action as flag change callback will handle hiding
495         if (rotateSuggestionsDisabled) return;
496 
497         rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
498     }
499 
500     /** Restores the System UI flags saved state to {@link NavigationBarFragment}. */
restoreSystemUiVisibilityState()501     public void restoreSystemUiVisibilityState() {
502         final int barMode = computeBarMode(0, mSystemUiVisibility);
503         if (barMode != -1) {
504             mNavigationBarMode = barMode;
505         }
506         checkNavBarModes();
507         mAutoHideController.touchAutoHide();
508 
509         mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
510                 true /* nbModeChanged */, mNavigationBarMode, false /* navbarColorManagedByIme */);
511     }
512 
513     @Override
setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean navbarColorManagedByIme)514     public void setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis,
515             int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds,
516             boolean navbarColorManagedByIme) {
517         if (displayId != mDisplayId) {
518             return;
519         }
520         final int oldVal = mSystemUiVisibility;
521         final int newVal = (oldVal & ~mask) | (vis & mask);
522         final int diff = newVal ^ oldVal;
523         boolean nbModeChanged = false;
524         if (diff != 0) {
525             mSystemUiVisibility = newVal;
526 
527             // update navigation bar mode
528             final int nbMode = getView() == null
529                     ? -1 : computeBarMode(oldVal, newVal);
530             nbModeChanged = nbMode != -1;
531             if (nbModeChanged) {
532                 if (mNavigationBarMode != nbMode) {
533                     if (mNavigationBarMode == MODE_TRANSPARENT
534                             || mNavigationBarMode == MODE_LIGHTS_OUT_TRANSPARENT) {
535                         mNavigationBarView.hideRecentsOnboarding();
536                     }
537                     mNavigationBarMode = nbMode;
538                     checkNavBarModes();
539                 }
540                 mAutoHideController.touchAutoHide();
541             }
542         }
543         mLightBarController.onNavigationVisibilityChanged(
544                 vis, mask, nbModeChanged, mNavigationBarMode, navbarColorManagedByIme);
545     }
546 
computeBarMode(int oldVis, int newVis)547     private @TransitionMode int computeBarMode(int oldVis, int newVis) {
548         final int oldMode = barMode(oldVis);
549         final int newMode = barMode(newVis);
550         if (oldMode == newMode) {
551             return -1; // no mode change
552         }
553         return newMode;
554     }
555 
barMode(int vis)556     private @TransitionMode int barMode(int vis) {
557         final int lightsOutTransparent =
558                 View.SYSTEM_UI_FLAG_LOW_PROFILE | View.NAVIGATION_BAR_TRANSIENT;
559         if ((vis & View.NAVIGATION_BAR_TRANSIENT) != 0) {
560             return MODE_SEMI_TRANSPARENT;
561         } else if ((vis & View.NAVIGATION_BAR_TRANSLUCENT) != 0) {
562             return MODE_TRANSLUCENT;
563         } else if ((vis & lightsOutTransparent) == lightsOutTransparent) {
564             return MODE_LIGHTS_OUT_TRANSPARENT;
565         } else if ((vis & View.NAVIGATION_BAR_TRANSPARENT) != 0) {
566             return MODE_TRANSPARENT;
567         } else if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
568             return MODE_LIGHTS_OUT;
569         } else {
570             return MODE_OPAQUE;
571         }
572     }
573 
574     @Override
disable(int displayId, int state1, int state2, boolean animate)575     public void disable(int displayId, int state1, int state2, boolean animate) {
576         if (displayId != mDisplayId) {
577             return;
578         }
579         // Navigation bar flags are in both state1 and state2.
580         final int masked = state1 & (StatusBarManager.DISABLE_HOME
581                 | StatusBarManager.DISABLE_RECENT
582                 | StatusBarManager.DISABLE_BACK
583                 | StatusBarManager.DISABLE_SEARCH);
584         if (masked != mDisabledFlags1) {
585             mDisabledFlags1 = masked;
586             if (mNavigationBarView != null) {
587                 mNavigationBarView.setDisabledFlags(state1);
588             }
589             updateScreenPinningGestures();
590         }
591 
592         // Only default display supports rotation suggestions.
593         if (mIsOnDefaultDisplay) {
594             final int masked2 = state2 & (StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
595             if (masked2 != mDisabledFlags2) {
596                 mDisabledFlags2 = masked2;
597                 setDisabled2Flags(masked2);
598             }
599         }
600     }
601 
setDisabled2Flags(int state2)602     private void setDisabled2Flags(int state2) {
603         // Method only called on change of disable2 flags
604         if (mNavigationBarView != null) {
605             mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2);
606         }
607     }
608 
609     // ----- Internal stuff -----
610 
refreshLayout(int layoutDirection)611     private void refreshLayout(int layoutDirection) {
612         if (mNavigationBarView != null) {
613             mNavigationBarView.setLayoutDirection(layoutDirection);
614         }
615     }
616 
shouldDisableNavbarGestures()617     private boolean shouldDisableNavbarGestures() {
618         return !mDeviceProvisionedController.isDeviceProvisioned()
619                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
620     }
621 
repositionNavigationBar()622     private void repositionNavigationBar() {
623         if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
624 
625         prepareNavigationBarView();
626 
627         mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
628                 ((View) mNavigationBarView.getParent()).getLayoutParams());
629     }
630 
updateScreenPinningGestures()631     private void updateScreenPinningGestures() {
632         if (mNavigationBarView == null) {
633             return;
634         }
635 
636         // Change the cancel pin gesture to home and back if recents button is invisible
637         boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible();
638         ButtonDispatcher backButton = mNavigationBarView.getBackButton();
639         if (recentsVisible) {
640             backButton.setOnLongClickListener(this::onLongPressBackRecents);
641         } else {
642             backButton.setOnLongClickListener(this::onLongPressBackHome);
643         }
644     }
645 
notifyNavigationBarScreenOn()646     private void notifyNavigationBarScreenOn() {
647         mNavigationBarView.updateNavButtonIcons();
648     }
649 
prepareNavigationBarView()650     private void prepareNavigationBarView() {
651         mNavigationBarView.reorient();
652 
653         ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
654         recentsButton.setOnClickListener(this::onRecentsClick);
655         recentsButton.setOnTouchListener(this::onRecentsTouch);
656         recentsButton.setLongClickable(true);
657         recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
658 
659         ButtonDispatcher backButton = mNavigationBarView.getBackButton();
660         backButton.setLongClickable(true);
661 
662         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
663         homeButton.setOnTouchListener(this::onHomeTouch);
664         homeButton.setOnLongClickListener(this::onHomeLongClick);
665 
666         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
667         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
668         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
669         updateAccessibilityServicesState(mAccessibilityManager);
670 
671         updateScreenPinningGestures();
672     }
673 
onHomeTouch(View v, MotionEvent event)674     private boolean onHomeTouch(View v, MotionEvent event) {
675         if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
676             return true;
677         }
678         // If an incoming call is ringing, HOME is totally disabled.
679         // (The user is already on the InCallUI at this point,
680         // and his ONLY options are to answer or reject the call.)
681         switch (event.getAction()) {
682             case MotionEvent.ACTION_DOWN:
683                 mHomeBlockedThisTouch = false;
684                 TelecomManager telecomManager =
685                         getContext().getSystemService(TelecomManager.class);
686                 if (telecomManager != null && telecomManager.isRinging()) {
687                     if (mStatusBar.isKeyguardShowing()) {
688                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
689                                 "No heads up");
690                         mHomeBlockedThisTouch = true;
691                         return true;
692                     }
693                 }
694                 break;
695             case MotionEvent.ACTION_UP:
696             case MotionEvent.ACTION_CANCEL:
697                 mStatusBar.awakenDreams();
698                 break;
699         }
700         return false;
701     }
702 
onVerticalChanged(boolean isVertical)703     private void onVerticalChanged(boolean isVertical) {
704         mStatusBar.setQsScrimEnabled(!isVertical);
705     }
706 
onNavigationTouch(View v, MotionEvent event)707     private boolean onNavigationTouch(View v, MotionEvent event) {
708         mAutoHideController.checkUserAutoHide(event);
709         return false;
710     }
711 
712     @VisibleForTesting
onHomeLongClick(View v)713     boolean onHomeLongClick(View v) {
714         if (!mNavigationBarView.isRecentsButtonVisible()
715                 && ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
716             return onLongPressBackHome(v);
717         }
718         if (shouldDisableNavbarGestures()) {
719             return false;
720         }
721         mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
722         Bundle args  = new Bundle();
723         args.putInt(
724                 AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);
725         mAssistManager.startAssist(args);
726         mStatusBar.awakenDreams();
727 
728         if (mNavigationBarView != null) {
729             mNavigationBarView.abortCurrentGesture();
730         }
731         return true;
732     }
733 
734     // additional optimization when we have software system buttons - start loading the recent
735     // tasks on touch down
onRecentsTouch(View v, MotionEvent event)736     private boolean onRecentsTouch(View v, MotionEvent event) {
737         int action = event.getAction() & MotionEvent.ACTION_MASK;
738         if (action == MotionEvent.ACTION_DOWN) {
739             mCommandQueue.preloadRecentApps();
740         } else if (action == MotionEvent.ACTION_CANCEL) {
741             mCommandQueue.cancelPreloadRecentApps();
742         } else if (action == MotionEvent.ACTION_UP) {
743             if (!v.isPressed()) {
744                 mCommandQueue.cancelPreloadRecentApps();
745             }
746         }
747         return false;
748     }
749 
onRecentsClick(View v)750     private void onRecentsClick(View v) {
751         if (LatencyTracker.isEnabled(getContext())) {
752             LatencyTracker.getInstance(getContext()).onActionStart(
753                     LatencyTracker.ACTION_TOGGLE_RECENTS);
754         }
755         mStatusBar.awakenDreams();
756         mCommandQueue.toggleRecentApps();
757     }
758 
onLongPressBackHome(View v)759     private boolean onLongPressBackHome(View v) {
760         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
761     }
762 
onLongPressBackRecents(View v)763     private boolean onLongPressBackRecents(View v) {
764         return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
765     }
766 
767     /**
768      * This handles long-press of both back and recents/home. Back is the common button with
769      * combination of recents if it is visible or home if recents is invisible.
770      * They are handled together to capture them both being long-pressed
771      * at the same time to exit screen pinning (lock task).
772      *
773      * When accessibility mode is on, only a long-press from recents/home
774      * is required to exit.
775      *
776      * In all other circumstances we try to pass through long-press events
777      * for Back, so that apps can still use it.  Which can be from two things.
778      * 1) Not currently in screen pinning (lock task).
779      * 2) Back is long-pressed without recents/home.
780      */
onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2)781     private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) {
782         try {
783             boolean sendBackLongPress = false;
784             IActivityTaskManager activityManager = ActivityTaskManager.getService();
785             boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
786             boolean inLockTaskMode = activityManager.isInLockTaskMode();
787             boolean stopLockTaskMode = false;
788             try {
789                 if (inLockTaskMode && !touchExplorationEnabled) {
790                     long time = System.currentTimeMillis();
791 
792                     // If we recently long-pressed the other button then they were
793                     // long-pressed 'together'
794                     if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
795                         stopLockTaskMode = true;
796                         return true;
797                     } else if (v.getId() == btnId1) {
798                         ButtonDispatcher button = btnId2 == R.id.recent_apps
799                                 ? mNavigationBarView.getRecentsButton()
800                                 : mNavigationBarView.getHomeButton();
801                         if (!button.getCurrentView().isPressed()) {
802                             // If we aren't pressing recents/home right now then they presses
803                             // won't be together, so send the standard long-press action.
804                             sendBackLongPress = true;
805                         }
806                     }
807                     mLastLockToAppLongPress = time;
808                 } else {
809                     // If this is back still need to handle sending the long-press event.
810                     if (v.getId() == btnId1) {
811                         sendBackLongPress = true;
812                     } else if (touchExplorationEnabled && inLockTaskMode) {
813                         // When in accessibility mode a long press that is recents/home (not back)
814                         // should stop lock task.
815                         stopLockTaskMode = true;
816                         return true;
817                     } else if (v.getId() == btnId2) {
818                         return btnId2 == R.id.recent_apps
819                                 ? onLongPressRecents()
820                                 : onHomeLongClick(
821                                         mNavigationBarView.getHomeButton().getCurrentView());
822                     }
823                 }
824             } finally {
825                 if (stopLockTaskMode) {
826                     activityManager.stopSystemLockTaskMode();
827                     // When exiting refresh disabled flags.
828                     mNavigationBarView.updateNavButtonIcons();
829                 }
830             }
831 
832             if (sendBackLongPress) {
833                 KeyButtonView keyButtonView = (KeyButtonView) v;
834                 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
835                 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
836                 return true;
837             }
838         } catch (RemoteException e) {
839             Log.d(TAG, "Unable to reach activity manager", e);
840         }
841         return false;
842     }
843 
onLongPressRecents()844     private boolean onLongPressRecents() {
845         if (mRecents == null || !ActivityTaskManager.supportsMultiWindow(getContext())
846                 || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
847                 || ActivityManager.isLowRamDeviceStatic()
848                 // If we are connected to the overview service, then disable the recents button
849                 || mOverviewProxyService.getProxy() != null) {
850             return false;
851         }
852 
853         return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
854                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
855     }
856 
onAccessibilityClick(View v)857     private void onAccessibilityClick(View v) {
858         final Display display = v.getDisplay();
859         mAccessibilityManager.notifyAccessibilityButtonClicked(
860                 display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
861     }
862 
onAccessibilityLongClick(View v)863     private boolean onAccessibilityLongClick(View v) {
864         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
865         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
866         v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
867         return true;
868     }
869 
updateAccessibilityServicesState(AccessibilityManager accessibilityManager)870     private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
871         boolean[] feedbackEnabled = new boolean[1];
872         int a11yFlags = getA11yButtonState(feedbackEnabled);
873 
874         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
875         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
876         mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
877 
878         updateSystemUiStateFlags(a11yFlags);
879     }
880 
updateSystemUiStateFlags(int a11yFlags)881     public void updateSystemUiStateFlags(int a11yFlags) {
882         if (a11yFlags < 0) {
883             a11yFlags = getA11yButtonState(null);
884         }
885         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
886         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
887         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE,
888                 clickable, mDisplayId);
889         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE,
890                 longClickable, mDisplayId);
891         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_NAV_BAR_HIDDEN,
892                 !isNavBarWindowVisible(), mDisplayId);
893     }
894 
895     /**
896      * Returns the system UI flags corresponding the the current accessibility button state
897      * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
898      */
getA11yButtonState(@ullable boolean[] outFeedbackEnabled)899     public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
900         int requestingServices = 0;
901         try {
902             if (Settings.Secure.getIntForUser(mContentResolver,
903                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
904                     UserHandle.USER_CURRENT) == 1) {
905                 requestingServices++;
906             }
907         } catch (Settings.SettingNotFoundException e) {
908         }
909 
910         boolean feedbackEnabled = false;
911         // AccessibilityManagerService resolves services for the current user since the local
912         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
913         final List<AccessibilityServiceInfo> services =
914                 mAccessibilityManager.getEnabledAccessibilityServiceList(
915                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
916         for (int i = services.size() - 1; i >= 0; --i) {
917             AccessibilityServiceInfo info = services.get(i);
918             if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
919                 requestingServices++;
920             }
921 
922             if (info.feedbackType != 0 && info.feedbackType !=
923                     AccessibilityServiceInfo.FEEDBACK_GENERIC) {
924                 feedbackEnabled = true;
925             }
926         }
927 
928         if (outFeedbackEnabled != null) {
929             outFeedbackEnabled[0] = feedbackEnabled;
930         }
931 
932         return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
933                 | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
934     }
935 
sendAssistantAvailability(boolean available)936     private void sendAssistantAvailability(boolean available) {
937         if (mOverviewProxyService.getProxy() != null) {
938             try {
939                 mOverviewProxyService.getProxy().onAssistantAvailable(available
940                         && QuickStepContract.isGesturalMode(mNavBarMode));
941             } catch (RemoteException e) {
942                 Log.w(TAG, "Unable to send assistant availability data to launcher");
943             }
944         }
945     }
946 
947     // ----- Methods that DisplayNavigationBarController talks to -----
948 
949     /** Applies auto dimming animation on navigation bar when touched. */
touchAutoDim()950     public void touchAutoDim() {
951         getBarTransitions().setAutoDim(false);
952         mHandler.removeCallbacks(mAutoDim);
953         int state = mStatusBarStateController.getState();
954         if (state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED) {
955             mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
956         }
957     }
958 
setLightBarController(LightBarController lightBarController)959     public void setLightBarController(LightBarController lightBarController) {
960         mLightBarController = lightBarController;
961         mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
962     }
963 
964     /** Sets {@link AutoHideController} to the navigation bar. */
setAutoHideController(AutoHideController autoHideController)965     public void setAutoHideController(AutoHideController autoHideController) {
966         mAutoHideController = autoHideController;
967         mAutoHideController.setNavigationBar(this);
968     }
969 
isSemiTransparent()970     public boolean isSemiTransparent() {
971         return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
972     }
973 
checkBarModes()974     private void checkBarModes() {
975         // We only have status bar on default display now.
976         if (mIsOnDefaultDisplay) {
977             mStatusBar.checkBarModes();
978         } else {
979             checkNavBarModes();
980         }
981     }
982 
isNavBarWindowVisible()983     public boolean isNavBarWindowVisible() {
984         return mNavigationBarWindowState == WINDOW_STATE_SHOWING;
985     }
986 
987     /**
988      * Checks current navigation bar mode and make transitions.
989      */
checkNavBarModes()990     public void checkNavBarModes() {
991         final boolean anim = mStatusBar.isDeviceInteractive()
992                 && mNavigationBarWindowState != WINDOW_STATE_HIDDEN;
993         mNavigationBarView.getBarTransitions().transitionTo(mNavigationBarMode, anim);
994     }
995 
996     @Override
onNavigationModeChanged(int mode)997     public void onNavigationModeChanged(int mode) {
998         mNavBarMode = mode;
999         updateScreenPinningGestures();
1000 
1001         // Workaround for b/132825155, for secondary users, we currently don't receive configuration
1002         // changes on overlay package change since SystemUI runs for the system user. In this case,
1003         // trigger a new configuration change to ensure that the nav bar is updated in the same way.
1004         int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
1005         if (userId != UserHandle.USER_SYSTEM) {
1006             mHandler.post(() -> {
1007                 FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
1008                 fragmentHost.reloadFragments();
1009             });
1010         }
1011     }
1012 
disableAnimationsDuringHide(long delay)1013     public void disableAnimationsDuringHide(long delay) {
1014         mNavigationBarView.setLayoutTransitionsEnabled(false);
1015         mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
1016                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
1017     }
1018 
1019     /**
1020      * Performs transitions on navigation bar.
1021      *
1022      * @param barMode transition bar mode.
1023      * @param animate shows animations if {@code true}.
1024      */
transitionTo(@ransitionMode int barMode, boolean animate)1025     public void transitionTo(@TransitionMode int barMode, boolean animate) {
1026         getBarTransitions().transitionTo(barMode, animate);
1027     }
1028 
getBarTransitions()1029     public NavigationBarTransitions getBarTransitions() {
1030         return mNavigationBarView.getBarTransitions();
1031     }
1032 
finishBarAnimations()1033     public void finishBarAnimations() {
1034         mNavigationBarView.getBarTransitions().finishAnimations();
1035     }
1036 
1037     private final AccessibilityServicesStateChangeListener mAccessibilityListener =
1038             this::updateAccessibilityServicesState;
1039 
1040     private class MagnificationContentObserver extends ContentObserver {
1041 
MagnificationContentObserver(Handler handler)1042         public MagnificationContentObserver(Handler handler) {
1043             super(handler);
1044         }
1045 
1046         @Override
onChange(boolean selfChange)1047         public void onChange(boolean selfChange) {
1048             NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
1049         }
1050     }
1051 
1052     private final Consumer<Integer> mRotationWatcher = rotation -> {
1053         if (mNavigationBarView != null
1054                 && mNavigationBarView.needsReorient(rotation)) {
1055             repositionNavigationBar();
1056         }
1057     };
1058 
1059     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1060         @Override
1061         public void onReceive(Context context, Intent intent) {
1062             String action = intent.getAction();
1063             if (Intent.ACTION_SCREEN_OFF.equals(action)
1064                     || Intent.ACTION_SCREEN_ON.equals(action)) {
1065                 notifyNavigationBarScreenOn();
1066 
1067                 if (Intent.ACTION_SCREEN_ON.equals(action)) {
1068                     // Enabled and screen is on, start it again if enabled
1069                     if (NavBarTintController.isEnabled(getContext(), mNavBarMode)) {
1070                         mNavigationBarView.getTintController().start();
1071                     }
1072                 } else {
1073                     // Screen off disable it
1074                     mNavigationBarView.getTintController().stop();
1075                 }
1076             }
1077             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
1078                 // The accessibility settings may be different for the new user
1079                 updateAccessibilityServicesState(mAccessibilityManager);
1080             }
1081         }
1082     };
1083 
create(Context context, FragmentListener listener)1084     public static View create(Context context, FragmentListener listener) {
1085         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1086                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
1087                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
1088                 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
1089                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1090                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1091                         | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1092                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
1093                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
1094                 PixelFormat.TRANSLUCENT);
1095         lp.token = new Binder();
1096         lp.setTitle("NavigationBar" + context.getDisplayId());
1097         lp.accessibilityTitle = context.getString(R.string.nav_bar);
1098         lp.windowAnimations = 0;
1099         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
1100 
1101         View navigationBarView = LayoutInflater.from(context).inflate(
1102                 R.layout.navigation_bar_window, null);
1103 
1104         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
1105         if (navigationBarView == null) return null;
1106 
1107         final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
1108                 .create(NavigationBarFragment.class);
1109         navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
1110             @Override
1111             public void onViewAttachedToWindow(View v) {
1112                 final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
1113                 fragmentHost.getFragmentManager().beginTransaction()
1114                         .replace(R.id.navigation_bar_frame, fragment, TAG)
1115                         .commit();
1116                 fragmentHost.addTagListener(TAG, listener);
1117             }
1118 
1119             @Override
1120             public void onViewDetachedFromWindow(View v) {
1121                 FragmentHostManager.removeAndDestroy(v);
1122             }
1123         });
1124         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
1125         return navigationBarView;
1126     }
1127 
1128     @VisibleForTesting
getNavigationIconHints()1129     int getNavigationIconHints() {
1130         return mNavigationIconHints;
1131     }
1132 }
1133