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