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_SHOWING;
20 import static android.app.StatusBarManager.windowStateToString;
21 
22 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
23 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
24 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
25 
26 import android.accessibilityservice.AccessibilityServiceInfo;
27 import android.annotation.Nullable;
28 import android.app.ActivityManager;
29 import android.app.ActivityManagerNative;
30 import android.app.Fragment;
31 import android.app.IActivityManager;
32 import android.app.StatusBarManager;
33 import android.content.BroadcastReceiver;
34 import android.content.ContentResolver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.res.Configuration;
39 import android.database.ContentObserver;
40 import android.graphics.PixelFormat;
41 import android.graphics.Rect;
42 import android.inputmethodservice.InputMethodService;
43 import android.os.Binder;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.IBinder;
47 import android.os.Message;
48 import android.os.PowerManager;
49 import android.os.RemoteException;
50 import android.os.UserHandle;
51 import android.provider.Settings;
52 import android.support.annotation.VisibleForTesting;
53 import android.telecom.TelecomManager;
54 import android.text.TextUtils;
55 import android.util.Log;
56 import android.view.IRotationWatcher.Stub;
57 import android.view.KeyEvent;
58 import android.view.LayoutInflater;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.view.WindowManager;
63 import android.view.WindowManager.LayoutParams;
64 import android.view.WindowManagerGlobal;
65 import android.view.accessibility.AccessibilityEvent;
66 import android.view.accessibility.AccessibilityManager;
67 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
68 
69 import com.android.internal.logging.MetricsLogger;
70 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
71 import com.android.keyguard.LatencyTracker;
72 import com.android.systemui.Dependency;
73 import com.android.systemui.R;
74 import com.android.systemui.SysUiServiceProvider;
75 import com.android.systemui.assist.AssistManager;
76 import com.android.systemui.fragments.FragmentHostManager;
77 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
78 import com.android.systemui.recents.Recents;
79 import com.android.systemui.stackdivider.Divider;
80 import com.android.systemui.statusbar.CommandQueue;
81 import com.android.systemui.statusbar.CommandQueue.Callbacks;
82 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
83 import com.android.systemui.statusbar.policy.KeyButtonView;
84 import com.android.systemui.statusbar.stack.StackStateAnimator;
85 
86 import java.io.FileDescriptor;
87 import java.io.PrintWriter;
88 import java.util.List;
89 import java.util.Locale;
90 
91 /**
92  * Fragment containing the NavigationBarFragment. Contains logic for what happens
93  * on clicks and view states of the nav bar.
94  */
95 public class NavigationBarFragment extends Fragment implements Callbacks {
96 
97     private static final String TAG = "NavigationBar";
98     private static final boolean DEBUG = false;
99     private static final String EXTRA_DISABLE_STATE = "disabled_state";
100 
101     /** Allow some time inbetween the long press for back and recents. */
102     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
103 
104     protected NavigationBarView mNavigationBarView = null;
105     protected AssistManager mAssistManager;
106 
107     private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
108 
109     private int mNavigationIconHints = 0;
110     private int mNavigationBarMode;
111     private AccessibilityManager mAccessibilityManager;
112     private MagnificationContentObserver mMagnificationObserver;
113     private ContentResolver mContentResolver;
114 
115     private int mDisabledFlags1;
116     private StatusBar mStatusBar;
117     private Recents mRecents;
118     private Divider mDivider;
119     private WindowManager mWindowManager;
120     private CommandQueue mCommandQueue;
121     private long mLastLockToAppLongPress;
122 
123     private Locale mLocale;
124     private int mLayoutDirection;
125 
126     private int mSystemUiVisibility;
127     private LightBarController mLightBarController;
128 
129     public boolean mHomeBlockedThisTouch;
130 
131     // ----- Fragment Lifecycle Callbacks -----
132 
133     @Override
onCreate(@ullable Bundle savedInstanceState)134     public void onCreate(@Nullable Bundle savedInstanceState) {
135         super.onCreate(savedInstanceState);
136         mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
137         mCommandQueue.addCallbacks(this);
138         mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
139         mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
140         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
141         mWindowManager = getContext().getSystemService(WindowManager.class);
142         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
143         Dependency.get(AccessibilityManagerWrapper.class).addCallback(
144                 mAccessibilityListener);
145         mContentResolver = getContext().getContentResolver();
146         mMagnificationObserver = new MagnificationContentObserver(
147                 getContext().getMainThreadHandler());
148         mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
149                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
150                 mMagnificationObserver, UserHandle.USER_ALL);
151 
152         if (savedInstanceState != null) {
153             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
154         }
155         mAssistManager = Dependency.get(AssistManager.class);
156 
157         try {
158             WindowManagerGlobal.getWindowManagerService()
159                     .watchRotation(mRotationWatcher, getContext().getDisplay().getDisplayId());
160         } catch (RemoteException e) {
161             throw e.rethrowFromSystemServer();
162         }
163     }
164 
165     @Override
onDestroy()166     public void onDestroy() {
167         super.onDestroy();
168         mCommandQueue.removeCallbacks(this);
169         Dependency.get(AccessibilityManagerWrapper.class).removeCallback(
170                 mAccessibilityListener);
171         mContentResolver.unregisterContentObserver(mMagnificationObserver);
172         try {
173             WindowManagerGlobal.getWindowManagerService()
174                     .removeRotationWatcher(mRotationWatcher);
175         } catch (RemoteException e) {
176             throw e.rethrowFromSystemServer();
177         }
178     }
179 
180     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)181     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
182             Bundle savedInstanceState) {
183         return inflater.inflate(R.layout.navigation_bar, container, false);
184     }
185 
186     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)187     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
188         super.onViewCreated(view, savedInstanceState);
189         mNavigationBarView = (NavigationBarView) view;
190 
191         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
192         mNavigationBarView.setComponents(mRecents, mDivider);
193         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
194         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
195         if (savedInstanceState != null) {
196             mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
197         }
198 
199         prepareNavigationBarView();
200         checkNavBarModes();
201 
202         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
203         filter.addAction(Intent.ACTION_SCREEN_ON);
204         getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
205         PowerManager pm = getContext().getSystemService(PowerManager.class);
206         notifyNavigationBarScreenOn(pm.isScreenOn());
207     }
208 
209     @Override
onDestroyView()210     public void onDestroyView() {
211         super.onDestroyView();
212         mNavigationBarView.getLightTransitionsController().destroy(getContext());
213         getContext().unregisterReceiver(mBroadcastReceiver);
214     }
215 
216     @Override
onSaveInstanceState(Bundle outState)217     public void onSaveInstanceState(Bundle outState) {
218         super.onSaveInstanceState(outState);
219         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
220         if (mNavigationBarView != null) {
221             mNavigationBarView.getLightTransitionsController().saveState(outState);
222         }
223     }
224 
225     @Override
onConfigurationChanged(Configuration newConfig)226     public void onConfigurationChanged(Configuration newConfig) {
227         super.onConfigurationChanged(newConfig);
228         final Locale locale = getContext().getResources().getConfiguration().locale;
229         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
230         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
231             if (DEBUG) {
232                 Log.v(TAG, String.format(
233                         "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
234                         locale, ld));
235             }
236             mLocale = locale;
237             mLayoutDirection = ld;
238             refreshLayout(ld);
239         }
240         repositionNavigationBar();
241     }
242 
243     @Override
dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args)244     public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
245         if (mNavigationBarView != null) {
246             pw.print("  mNavigationBarWindowState=");
247             pw.println(windowStateToString(mNavigationBarWindowState));
248             pw.print("  mNavigationBarMode=");
249             pw.println(BarTransitions.modeToString(mNavigationBarMode));
250             dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
251         }
252 
253         pw.print("  mNavigationBarView=");
254         if (mNavigationBarView == null) {
255             pw.println("null");
256         } else {
257             mNavigationBarView.dump(fd, pw, args);
258         }
259     }
260 
261     // ----- CommandQueue Callbacks -----
262 
263     @Override
setImeWindowStatus(IBinder token, int vis, int backDisposition, boolean showImeSwitcher)264     public void setImeWindowStatus(IBinder token, int vis, int backDisposition,
265             boolean showImeSwitcher) {
266         boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
267         int hints = mNavigationIconHints;
268         if ((backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || imeShown) {
269             hints |= NAVIGATION_HINT_BACK_ALT;
270         } else {
271             hints &= ~NAVIGATION_HINT_BACK_ALT;
272         }
273         if (showImeSwitcher) {
274             hints |= NAVIGATION_HINT_IME_SHOWN;
275         } else {
276             hints &= ~NAVIGATION_HINT_IME_SHOWN;
277         }
278         if (hints == mNavigationIconHints) return;
279 
280         mNavigationIconHints = hints;
281 
282         if (mNavigationBarView != null) {
283             mNavigationBarView.setNavigationIconHints(hints);
284         }
285         mStatusBar.checkBarModes();
286     }
287 
288     @Override
topAppWindowChanged(boolean showMenu)289     public void topAppWindowChanged(boolean showMenu) {
290         if (mNavigationBarView != null) {
291             mNavigationBarView.setMenuVisibility(showMenu);
292         }
293     }
294 
295     @Override
setWindowState(int window, int state)296     public void setWindowState(int window, int state) {
297         if (mNavigationBarView != null
298                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
299                 && mNavigationBarWindowState != state) {
300             mNavigationBarWindowState = state;
301             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
302         }
303     }
304 
305     // Injected from StatusBar at creation.
setCurrentSysuiVisibility(int systemUiVisibility)306     public void setCurrentSysuiVisibility(int systemUiVisibility) {
307         mSystemUiVisibility = systemUiVisibility;
308         mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
309                 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
310                 View.NAVIGATION_BAR_TRANSPARENT);
311         checkNavBarModes();
312         mStatusBar.touchAutoHide();
313         mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
314                 true /* nbModeChanged */, mNavigationBarMode);
315     }
316 
317     @Override
setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds)318     public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
319             int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
320         final int oldVal = mSystemUiVisibility;
321         final int newVal = (oldVal & ~mask) | (vis & mask);
322         final int diff = newVal ^ oldVal;
323         boolean nbModeChanged = false;
324         if (diff != 0) {
325             mSystemUiVisibility = newVal;
326 
327             // update navigation bar mode
328             final int nbMode = getView() == null
329                     ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
330                     View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
331                     View.NAVIGATION_BAR_TRANSPARENT);
332             nbModeChanged = nbMode != -1;
333             if (nbModeChanged) {
334                 if (mNavigationBarMode != nbMode) {
335                     mNavigationBarMode = nbMode;
336                     checkNavBarModes();
337                 }
338                 mStatusBar.touchAutoHide();
339             }
340         }
341 
342         mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged,
343                 mNavigationBarMode);
344     }
345 
346     @Override
disable(int state1, int state2, boolean animate)347     public void disable(int state1, int state2, boolean animate) {
348         // All navigation bar flags are in state1.
349         int masked = state1 & (StatusBarManager.DISABLE_HOME
350                 | StatusBarManager.DISABLE_RECENT
351                 | StatusBarManager.DISABLE_BACK
352                 | StatusBarManager.DISABLE_SEARCH);
353         if (masked != mDisabledFlags1) {
354             mDisabledFlags1 = masked;
355             if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
356         }
357     }
358 
359     // ----- Internal stuffz -----
360 
refreshLayout(int layoutDirection)361     private void refreshLayout(int layoutDirection) {
362         if (mNavigationBarView != null) {
363             mNavigationBarView.setLayoutDirection(layoutDirection);
364         }
365     }
366 
shouldDisableNavbarGestures()367     private boolean shouldDisableNavbarGestures() {
368         return !mStatusBar.isDeviceProvisioned()
369                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
370     }
371 
repositionNavigationBar()372     private void repositionNavigationBar() {
373         if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
374 
375         prepareNavigationBarView();
376 
377         mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
378                 ((View) mNavigationBarView.getParent()).getLayoutParams());
379     }
380 
notifyNavigationBarScreenOn(boolean screenOn)381     private void notifyNavigationBarScreenOn(boolean screenOn) {
382         mNavigationBarView.notifyScreenOn(screenOn);
383     }
384 
prepareNavigationBarView()385     private void prepareNavigationBarView() {
386         mNavigationBarView.reorient();
387 
388         ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
389         recentsButton.setOnClickListener(this::onRecentsClick);
390         recentsButton.setOnTouchListener(this::onRecentsTouch);
391         recentsButton.setLongClickable(true);
392         recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
393 
394         ButtonDispatcher backButton = mNavigationBarView.getBackButton();
395         backButton.setLongClickable(true);
396         backButton.setOnLongClickListener(this::onLongPressBackRecents);
397 
398         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
399         homeButton.setOnTouchListener(this::onHomeTouch);
400         homeButton.setOnLongClickListener(this::onHomeLongClick);
401 
402         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
403         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
404         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
405         updateAccessibilityServicesState(mAccessibilityManager);
406     }
407 
onHomeTouch(View v, MotionEvent event)408     private boolean onHomeTouch(View v, MotionEvent event) {
409         if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
410             return true;
411         }
412         // If an incoming call is ringing, HOME is totally disabled.
413         // (The user is already on the InCallUI at this point,
414         // and his ONLY options are to answer or reject the call.)
415         switch (event.getAction()) {
416             case MotionEvent.ACTION_DOWN:
417                 mHomeBlockedThisTouch = false;
418                 TelecomManager telecomManager =
419                         getContext().getSystemService(TelecomManager.class);
420                 if (telecomManager != null && telecomManager.isRinging()) {
421                     if (mStatusBar.isKeyguardShowing()) {
422                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
423                                 "No heads up");
424                         mHomeBlockedThisTouch = true;
425                         return true;
426                     }
427                 }
428                 break;
429             case MotionEvent.ACTION_UP:
430             case MotionEvent.ACTION_CANCEL:
431                 mStatusBar.awakenDreams();
432                 break;
433         }
434         return false;
435     }
436 
onVerticalChanged(boolean isVertical)437     private void onVerticalChanged(boolean isVertical) {
438         mStatusBar.setQsScrimEnabled(!isVertical);
439     }
440 
onNavigationTouch(View v, MotionEvent event)441     private boolean onNavigationTouch(View v, MotionEvent event) {
442         mStatusBar.checkUserAutohide(v, event);
443         return false;
444     }
445 
446     @VisibleForTesting
onHomeLongClick(View v)447     boolean onHomeLongClick(View v) {
448         if (shouldDisableNavbarGestures()) {
449             return false;
450         }
451         MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
452         mAssistManager.startAssist(new Bundle() /* args */);
453         mStatusBar.awakenDreams();
454         if (mNavigationBarView != null) {
455             mNavigationBarView.abortCurrentGesture();
456         }
457         return true;
458     }
459 
460     // additional optimization when we have software system buttons - start loading the recent
461     // tasks on touch down
onRecentsTouch(View v, MotionEvent event)462     private boolean onRecentsTouch(View v, MotionEvent event) {
463         int action = event.getAction() & MotionEvent.ACTION_MASK;
464         if (action == MotionEvent.ACTION_DOWN) {
465             mCommandQueue.preloadRecentApps();
466         } else if (action == MotionEvent.ACTION_CANCEL) {
467             mCommandQueue.cancelPreloadRecentApps();
468         } else if (action == MotionEvent.ACTION_UP) {
469             if (!v.isPressed()) {
470                 mCommandQueue.cancelPreloadRecentApps();
471             }
472         }
473         return false;
474     }
475 
onRecentsClick(View v)476     private void onRecentsClick(View v) {
477         if (LatencyTracker.isEnabled(getContext())) {
478             LatencyTracker.getInstance(getContext()).onActionStart(
479                     LatencyTracker.ACTION_TOGGLE_RECENTS);
480         }
481         mStatusBar.awakenDreams();
482         mCommandQueue.toggleRecentApps();
483     }
484 
485     /**
486      * This handles long-press of both back and recents.  They are
487      * handled together to capture them both being long-pressed
488      * at the same time to exit screen pinning (lock task).
489      *
490      * When accessibility mode is on, only a long-press from recents
491      * is required to exit.
492      *
493      * In all other circumstances we try to pass through long-press events
494      * for Back, so that apps can still use it.  Which can be from two things.
495      * 1) Not currently in screen pinning (lock task).
496      * 2) Back is long-pressed without recents.
497      */
onLongPressBackRecents(View v)498     private boolean onLongPressBackRecents(View v) {
499         try {
500             boolean sendBackLongPress = false;
501             IActivityManager activityManager = ActivityManagerNative.getDefault();
502             boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
503             boolean inLockTaskMode = activityManager.isInLockTaskMode();
504             if (inLockTaskMode && !touchExplorationEnabled) {
505                 long time = System.currentTimeMillis();
506                 // If we recently long-pressed the other button then they were
507                 // long-pressed 'together'
508                 if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
509                     activityManager.stopLockTaskMode();
510                     // When exiting refresh disabled flags.
511                     mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
512                     return true;
513                 } else if ((v.getId() == R.id.back)
514                         && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) {
515                     // If we aren't pressing recents right now then they presses
516                     // won't be together, so send the standard long-press action.
517                     sendBackLongPress = true;
518                 }
519                 mLastLockToAppLongPress = time;
520             } else {
521                 // If this is back still need to handle sending the long-press event.
522                 if (v.getId() == R.id.back) {
523                     sendBackLongPress = true;
524                 } else if (touchExplorationEnabled && inLockTaskMode) {
525                     // When in accessibility mode a long press that is recents (not back)
526                     // should stop lock task.
527                     activityManager.stopLockTaskMode();
528                     // When exiting refresh disabled flags.
529                     mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
530                     return true;
531                 } else if (v.getId() == R.id.recent_apps) {
532                     return onLongPressRecents();
533                 }
534             }
535             if (sendBackLongPress) {
536                 KeyButtonView keyButtonView = (KeyButtonView) v;
537                 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
538                 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
539                 return true;
540             }
541         } catch (RemoteException e) {
542             Log.d(TAG, "Unable to reach activity manager", e);
543         }
544         return false;
545     }
546 
onLongPressRecents()547     private boolean onLongPressRecents() {
548         if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext())
549                 || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) {
550             return false;
551         }
552 
553         return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
554                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
555     }
556 
onAccessibilityClick(View v)557     private void onAccessibilityClick(View v) {
558         mAccessibilityManager.notifyAccessibilityButtonClicked();
559     }
560 
onAccessibilityLongClick(View v)561     private boolean onAccessibilityLongClick(View v) {
562         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
563         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
564         v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
565         return true;
566     }
567 
updateAccessibilityServicesState(AccessibilityManager accessibilityManager)568     private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
569         int requestingServices = 0;
570         try {
571             if (Settings.Secure.getIntForUser(mContentResolver,
572                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
573                     UserHandle.USER_CURRENT) == 1) {
574                 requestingServices++;
575             }
576         } catch (Settings.SettingNotFoundException e) {
577         }
578 
579         // AccessibilityManagerService resolves services for the current user since the local
580         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
581         final List<AccessibilityServiceInfo> services =
582                 accessibilityManager.getEnabledAccessibilityServiceList(
583                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
584         for (int i = services.size() - 1; i >= 0; --i) {
585             AccessibilityServiceInfo info = services.get(i);
586             if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
587                 requestingServices++;
588             }
589         }
590 
591         final boolean showAccessibilityButton = requestingServices >= 1;
592         final boolean targetSelection = requestingServices >= 2;
593         mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
594     }
595 
596     // ----- Methods that StatusBar talks to (should be minimized) -----
597 
setLightBarController(LightBarController lightBarController)598     public void setLightBarController(LightBarController lightBarController) {
599         mLightBarController = lightBarController;
600         mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
601     }
602 
isSemiTransparent()603     public boolean isSemiTransparent() {
604         return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
605     }
606 
onKeyguardOccludedChanged(boolean keyguardOccluded)607     public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
608         mNavigationBarView.onKeyguardOccludedChanged(keyguardOccluded);
609     }
610 
disableAnimationsDuringHide(long delay)611     public void disableAnimationsDuringHide(long delay) {
612         mNavigationBarView.setLayoutTransitionsEnabled(false);
613         mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
614                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
615     }
616 
getBarTransitions()617     public BarTransitions getBarTransitions() {
618         return mNavigationBarView.getBarTransitions();
619     }
620 
checkNavBarModes()621     public void checkNavBarModes() {
622         mStatusBar.checkBarMode(mNavigationBarMode,
623                 mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
624     }
625 
finishBarAnimations()626     public void finishBarAnimations() {
627         mNavigationBarView.getBarTransitions().finishAnimations();
628     }
629 
630     private final AccessibilityServicesStateChangeListener mAccessibilityListener =
631             this::updateAccessibilityServicesState;
632 
633     private class MagnificationContentObserver extends ContentObserver {
634 
MagnificationContentObserver(Handler handler)635         public MagnificationContentObserver(Handler handler) {
636             super(handler);
637         }
638 
639         @Override
onChange(boolean selfChange)640         public void onChange(boolean selfChange) {
641             NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
642         }
643     }
644 
645     private final Stub mRotationWatcher = new Stub() {
646         @Override
647         public void onRotationChanged(int rotation) throws RemoteException {
648             // We need this to be scheduled as early as possible to beat the redrawing of
649             // window in response to the orientation change.
650             Handler h = getView().getHandler();
651             Message msg = Message.obtain(h, () -> {
652                 if (mNavigationBarView != null
653                         && mNavigationBarView.needsReorient(rotation)) {
654                     repositionNavigationBar();
655                 }
656             });
657             msg.setAsynchronous(true);
658             h.sendMessageAtFrontOfQueue(msg);
659         }
660     };
661 
662     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
663         @Override
664         public void onReceive(Context context, Intent intent) {
665             String action = intent.getAction();
666             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
667                 notifyNavigationBarScreenOn(false);
668             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
669                 notifyNavigationBarScreenOn(true);
670             }
671         }
672     };
673 
create(Context context, FragmentListener listener)674     public static View create(Context context, FragmentListener listener) {
675         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
676                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
677                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
678                 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
679                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
680                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
681                         | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
682                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
683                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
684                 PixelFormat.TRANSLUCENT);
685         lp.token = new Binder();
686         // this will allow the navbar to run in an overlay on devices that support this
687         if (ActivityManager.isHighEndGfx()) {
688             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
689         }
690         lp.setTitle("NavigationBar");
691         lp.windowAnimations = 0;
692 
693         View navigationBarView = LayoutInflater.from(context).inflate(
694                 R.layout.navigation_bar_window, null);
695 
696         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
697         if (navigationBarView == null) return null;
698 
699         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
700         FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
701         NavigationBarFragment fragment = new NavigationBarFragment();
702         fragmentHost.getFragmentManager().beginTransaction()
703                 .replace(R.id.navigation_bar_frame, fragment, TAG)
704                 .commit();
705         fragmentHost.addTagListener(TAG, listener);
706         return navigationBarView;
707     }
708 }
709