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.internal.view.RotationPolicy.NATURAL_ROTATION;
23 
24 import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
25 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
26 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
27 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
28 import static com.android.systemui.OverviewProxyService.OverviewProxyListener;
29 
30 import android.accessibilityservice.AccessibilityServiceInfo;
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.ObjectAnimator;
34 import android.annotation.IdRes;
35 import android.annotation.Nullable;
36 import android.app.ActivityManager;
37 import android.app.ActivityManagerNative;
38 import android.app.Fragment;
39 import android.app.IActivityManager;
40 import android.app.StatusBarManager;
41 import android.content.BroadcastReceiver;
42 import android.content.ContentResolver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.res.Configuration;
47 import android.database.ContentObserver;
48 import android.graphics.PixelFormat;
49 import android.graphics.Rect;
50 import android.graphics.drawable.AnimatedVectorDrawable;
51 import android.inputmethodservice.InputMethodService;
52 import android.os.Binder;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.Message;
57 import android.os.RemoteException;
58 import android.os.UserHandle;
59 import android.provider.Settings;
60 import android.support.annotation.VisibleForTesting;
61 import android.telecom.TelecomManager;
62 import android.text.TextUtils;
63 import android.util.Log;
64 import android.view.IRotationWatcher.Stub;
65 import android.view.KeyEvent;
66 import android.view.LayoutInflater;
67 import android.view.MotionEvent;
68 import android.view.Surface;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.WindowManager;
72 import android.view.WindowManager.LayoutParams;
73 import android.view.WindowManagerGlobal;
74 import android.view.accessibility.AccessibilityEvent;
75 import android.view.accessibility.AccessibilityManager;
76 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
77 
78 import com.android.internal.logging.MetricsLogger;
79 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
80 import com.android.internal.util.LatencyTracker;
81 import com.android.systemui.Dependency;
82 import com.android.systemui.Interpolators;
83 import com.android.systemui.OverviewProxyService;
84 import com.android.systemui.R;
85 import com.android.systemui.SysUiServiceProvider;
86 import com.android.systemui.assist.AssistManager;
87 import com.android.systemui.fragments.FragmentHostManager;
88 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
89 import com.android.systemui.recents.Recents;
90 import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
91 import com.android.systemui.shared.system.ActivityManagerWrapper;
92 import com.android.systemui.stackdivider.Divider;
93 import com.android.systemui.statusbar.CommandQueue;
94 import com.android.systemui.statusbar.CommandQueue.Callbacks;
95 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
96 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
97 import com.android.systemui.statusbar.policy.KeyButtonView;
98 import com.android.systemui.statusbar.policy.RotationLockController;
99 import com.android.systemui.statusbar.stack.StackStateAnimator;
100 
101 import java.io.FileDescriptor;
102 import java.io.PrintWriter;
103 import java.util.List;
104 import java.util.Locale;
105 import java.util.Optional;
106 
107 /**
108  * Fragment containing the NavigationBarFragment. Contains logic for what happens
109  * on clicks and view states of the nav bar.
110  */
111 public class NavigationBarFragment extends Fragment implements Callbacks {
112 
113     public static final String TAG = "NavigationBar";
114     private static final boolean DEBUG = false;
115     private static final boolean DEBUG_ROTATION = true;
116     private static final String EXTRA_DISABLE_STATE = "disabled_state";
117     private static final String EXTRA_DISABLE2_STATE = "disabled2_state";
118 
119     private final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
120     private final static int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
121 
122     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
123 
124     /** Allow some time inbetween the long press for back and recents. */
125     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
126 
127     protected NavigationBarView mNavigationBarView = null;
128     protected AssistManager mAssistManager;
129 
130     private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
131 
132     private int mNavigationIconHints = 0;
133     private int mNavigationBarMode;
134     private boolean mAccessibilityFeedbackEnabled;
135     private AccessibilityManager mAccessibilityManager;
136     private MagnificationContentObserver mMagnificationObserver;
137     private ContentResolver mContentResolver;
138     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
139 
140     private int mDisabledFlags1;
141     private int mDisabledFlags2;
142     private StatusBar mStatusBar;
143     private Recents mRecents;
144     private Divider mDivider;
145     private WindowManager mWindowManager;
146     private CommandQueue mCommandQueue;
147     private long mLastLockToAppLongPress;
148 
149     private Locale mLocale;
150     private int mLayoutDirection;
151 
152     private int mSystemUiVisibility;
153     private LightBarController mLightBarController;
154 
155     private OverviewProxyService mOverviewProxyService;
156 
157     public boolean mHomeBlockedThisTouch;
158 
159     private int mLastRotationSuggestion;
160     private boolean mPendingRotationSuggestion;
161     private boolean mHoveringRotationSuggestion;
162     private RotationLockController mRotationLockController;
163     private TaskStackListenerImpl mTaskStackListener;
164 
165     private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
166     private final Runnable mCancelPendingRotationProposal =
167             () -> mPendingRotationSuggestion = false;
168     private Animator mRotateHideAnimator;
169     private ViewRippler mViewRippler = new ViewRippler();
170 
171     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
172         @Override
173         public void onConnectionChanged(boolean isConnected) {
174             mNavigationBarView.updateStates();
175             updateScreenPinningGestures();
176         }
177 
178         @Override
179         public void onQuickStepStarted() {
180             // Use navbar dragging as a signal to hide the rotate button
181             setRotateSuggestionButtonState(false);
182         }
183 
184         @Override
185         public void onInteractionFlagsChanged(@InteractionType int flags) {
186             mNavigationBarView.updateStates();
187             updateScreenPinningGestures();
188         }
189 
190         @Override
191         public void onBackButtonAlphaChanged(float alpha, boolean animate) {
192             final ButtonDispatcher backButton = mNavigationBarView.getBackButton();
193             backButton.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE);
194             backButton.setAlpha(alpha, animate);
195         }
196     };
197 
198     // ----- Fragment Lifecycle Callbacks -----
199 
200     @Override
onCreate(@ullable Bundle savedInstanceState)201     public void onCreate(@Nullable Bundle savedInstanceState) {
202         super.onCreate(savedInstanceState);
203         mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
204         mCommandQueue.addCallbacks(this);
205         mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
206         mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
207         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
208         mWindowManager = getContext().getSystemService(WindowManager.class);
209         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
210         Dependency.get(AccessibilityManagerWrapper.class).addCallback(
211                 mAccessibilityListener);
212         mContentResolver = getContext().getContentResolver();
213         mMagnificationObserver = new MagnificationContentObserver(
214                 getContext().getMainThreadHandler());
215         mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
216                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
217                 mMagnificationObserver, UserHandle.USER_ALL);
218 
219         if (savedInstanceState != null) {
220             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
221             mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
222         }
223         mAssistManager = Dependency.get(AssistManager.class);
224         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
225 
226         try {
227             WindowManagerGlobal.getWindowManagerService()
228                     .watchRotation(mRotationWatcher, getContext().getDisplay().getDisplayId());
229         } catch (RemoteException e) {
230             throw e.rethrowFromSystemServer();
231         }
232 
233         mRotationLockController = Dependency.get(RotationLockController.class);
234 
235         // Reset user rotation pref to match that of the WindowManager if starting in locked mode
236         // This will automatically happen when switching from auto-rotate to locked mode
237         if (mRotationLockController.isRotationLocked()) {
238             final int winRotation = mWindowManager.getDefaultDisplay().getRotation();
239             mRotationLockController.setRotationLockedAtAngle(true, winRotation);
240         }
241 
242         // Register the task stack listener
243         mTaskStackListener = new TaskStackListenerImpl();
244         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
245     }
246 
247     @Override
onDestroy()248     public void onDestroy() {
249         super.onDestroy();
250         mCommandQueue.removeCallbacks(this);
251         Dependency.get(AccessibilityManagerWrapper.class).removeCallback(
252                 mAccessibilityListener);
253         mContentResolver.unregisterContentObserver(mMagnificationObserver);
254         try {
255             WindowManagerGlobal.getWindowManagerService()
256                     .removeRotationWatcher(mRotationWatcher);
257         } catch (RemoteException e) {
258             throw e.rethrowFromSystemServer();
259         }
260 
261         // Unregister the task stack listener
262         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
263     }
264 
265     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)266     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
267             Bundle savedInstanceState) {
268         return inflater.inflate(R.layout.navigation_bar, container, false);
269     }
270 
271     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)272     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
273         super.onViewCreated(view, savedInstanceState);
274         mNavigationBarView = (NavigationBarView) view;
275 
276         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
277         mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
278         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
279         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
280         if (savedInstanceState != null) {
281             mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
282         }
283 
284         prepareNavigationBarView();
285         checkNavBarModes();
286 
287         setDisabled2Flags(mDisabledFlags2);
288 
289         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
290         filter.addAction(Intent.ACTION_SCREEN_ON);
291         filter.addAction(Intent.ACTION_USER_SWITCHED);
292         getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
293         notifyNavigationBarScreenOn();
294         mOverviewProxyService.addCallback(mOverviewProxyListener);
295     }
296 
297     @Override
onDestroyView()298     public void onDestroyView() {
299         super.onDestroyView();
300         mNavigationBarView.getLightTransitionsController().destroy(getContext());
301         mOverviewProxyService.removeCallback(mOverviewProxyListener);
302         getContext().unregisterReceiver(mBroadcastReceiver);
303     }
304 
305     @Override
onSaveInstanceState(Bundle outState)306     public void onSaveInstanceState(Bundle outState) {
307         super.onSaveInstanceState(outState);
308         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
309         outState.putInt(EXTRA_DISABLE2_STATE, mDisabledFlags2);
310         if (mNavigationBarView != null) {
311             mNavigationBarView.getLightTransitionsController().saveState(outState);
312         }
313     }
314 
315     @Override
onConfigurationChanged(Configuration newConfig)316     public void onConfigurationChanged(Configuration newConfig) {
317         super.onConfigurationChanged(newConfig);
318         final Locale locale = getContext().getResources().getConfiguration().locale;
319         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
320         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
321             if (DEBUG) {
322                 Log.v(TAG, String.format(
323                         "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
324                         locale, ld));
325             }
326             mLocale = locale;
327             mLayoutDirection = ld;
328             refreshLayout(ld);
329         }
330         repositionNavigationBar();
331     }
332 
333     @Override
dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args)334     public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
335         if (mNavigationBarView != null) {
336             pw.print("  mNavigationBarWindowState=");
337             pw.println(windowStateToString(mNavigationBarWindowState));
338             pw.print("  mNavigationBarMode=");
339             pw.println(BarTransitions.modeToString(mNavigationBarMode));
340             dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
341         }
342 
343         pw.print("  mNavigationBarView=");
344         if (mNavigationBarView == null) {
345             pw.println("null");
346         } else {
347             mNavigationBarView.dump(fd, pw, args);
348         }
349     }
350 
351     // ----- CommandQueue Callbacks -----
352 
353     @Override
setImeWindowStatus(IBinder token, int vis, int backDisposition, boolean showImeSwitcher)354     public void setImeWindowStatus(IBinder token, int vis, int backDisposition,
355             boolean showImeSwitcher) {
356         boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
357         int hints = mNavigationIconHints;
358         switch (backDisposition) {
359             case InputMethodService.BACK_DISPOSITION_DEFAULT:
360             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
361             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
362                 if (imeShown) {
363                     hints |= NAVIGATION_HINT_BACK_ALT;
364                 } else {
365                     hints &= ~NAVIGATION_HINT_BACK_ALT;
366                 }
367                 break;
368             case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
369                 hints &= ~NAVIGATION_HINT_BACK_ALT;
370                 break;
371         }
372         if (showImeSwitcher) {
373             hints |= NAVIGATION_HINT_IME_SHOWN;
374         } else {
375             hints &= ~NAVIGATION_HINT_IME_SHOWN;
376         }
377         if (hints == mNavigationIconHints) return;
378 
379         mNavigationIconHints = hints;
380 
381         if (mNavigationBarView != null) {
382             mNavigationBarView.setNavigationIconHints(hints);
383         }
384         mStatusBar.checkBarModes();
385     }
386 
387     @Override
topAppWindowChanged(boolean showMenu)388     public void topAppWindowChanged(boolean showMenu) {
389         if (mNavigationBarView != null) {
390             mNavigationBarView.setMenuVisibility(showMenu);
391         }
392     }
393 
394     @Override
setWindowState(int window, int state)395     public void setWindowState(int window, int state) {
396         if (mNavigationBarView != null
397                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
398                 && mNavigationBarWindowState != state) {
399             mNavigationBarWindowState = state;
400             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
401 
402             // If the navbar is visible, show the rotate button if there's a pending suggestion
403             if (state == WINDOW_STATE_SHOWING && mPendingRotationSuggestion) {
404                 showAndLogRotationSuggestion();
405             }
406         }
407     }
408 
409     @Override
onRotationProposal(final int rotation, boolean isValid)410     public void onRotationProposal(final int rotation, boolean isValid) {
411         final int winRotation = mWindowManager.getDefaultDisplay().getRotation();
412         final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(mDisabledFlags2);
413         if (DEBUG_ROTATION) {
414             Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
415                     + ", winRotation=" + Surface.rotationToString(winRotation)
416                     + ", isValid=" + isValid + ", mNavBarWindowState="
417                     + StatusBarManager.windowStateToString(mNavigationBarWindowState)
418                     + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
419                     + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null" :
420                         mNavigationBarView.isRotateButtonVisible()));
421         }
422 
423         // Respect the disabled flag, no need for action as flag change callback will handle hiding
424         if (rotateSuggestionsDisabled) return;
425 
426         // This method will be called on rotation suggestion changes even if the proposed rotation
427         // is not valid for the top app. Use invalid rotation choices as a signal to remove the
428         // rotate button if shown.
429         if (!isValid) {
430             setRotateSuggestionButtonState(false);
431             return;
432         }
433 
434         // If window rotation matches suggested rotation, remove any current suggestions
435         if (rotation == winRotation) {
436             getView().removeCallbacks(mRemoveRotationProposal);
437             setRotateSuggestionButtonState(false);
438             return;
439         }
440 
441         // Prepare to show the navbar icon by updating the icon style to change anim params
442         mLastRotationSuggestion = rotation; // Remember rotation for click
443         if (mNavigationBarView != null) {
444             final boolean rotationCCW = isRotationAnimationCCW(winRotation, rotation);
445             int style;
446             if (winRotation == Surface.ROTATION_0 || winRotation == Surface.ROTATION_180) {
447                 style = rotationCCW ? R.style.RotateButtonCCWStart90 :
448                         R.style.RotateButtonCWStart90;
449             } else { // 90 or 270
450                 style = rotationCCW ? R.style.RotateButtonCCWStart0 :
451                         R.style.RotateButtonCWStart0;
452             }
453             mNavigationBarView.updateRotateSuggestionButtonStyle(style, true);
454         }
455 
456         if (mNavigationBarWindowState != WINDOW_STATE_SHOWING) {
457             // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
458             // visible given some time limit.
459             mPendingRotationSuggestion = true;
460             getView().removeCallbacks(mCancelPendingRotationProposal);
461             getView().postDelayed(mCancelPendingRotationProposal,
462                     NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
463 
464         } else { // The navbar is visible so show the icon right away
465             showAndLogRotationSuggestion();
466         }
467     }
468 
onRotationSuggestionsDisabled()469     private void onRotationSuggestionsDisabled() {
470         // Immediately hide the rotate button and clear any planned removal
471         setRotateSuggestionButtonState(false, true);
472 
473         // This method can be called before view setup is done, ensure getView isn't null
474         final View v = getView();
475         if (v != null) v.removeCallbacks(mRemoveRotationProposal);
476     }
477 
showAndLogRotationSuggestion()478     private void showAndLogRotationSuggestion() {
479         setRotateSuggestionButtonState(true);
480         rescheduleRotationTimeout(false);
481         mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
482     }
483 
isRotationAnimationCCW(int from, int to)484     private boolean isRotationAnimationCCW(int from, int to) {
485         // All 180deg WM rotation animations are CCW, match that
486         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
487         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
488         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
489         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
490         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
491         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
492         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
493         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
494         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
495         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
496         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
497         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
498         return false; // Default
499     }
500 
setRotateSuggestionButtonState(final boolean visible)501     public void setRotateSuggestionButtonState(final boolean visible) {
502         setRotateSuggestionButtonState(visible, false);
503     }
504 
setRotateSuggestionButtonState(final boolean visible, final boolean force)505     public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
506         if (mNavigationBarView == null) return;
507 
508         // At any point the the button can become invisible because an a11y service became active.
509         // Similarly, a call to make the button visible may be rejected because an a11y service is
510         // active. Must account for this.
511 
512         ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
513         final boolean currentlyVisible = mNavigationBarView.isRotateButtonVisible();
514 
515         // Rerun a show animation to indicate change but don't rerun a hide animation
516         if (!visible && !currentlyVisible) return;
517 
518         View view = rotBtn.getCurrentView();
519         if (view == null) return;
520 
521         KeyButtonDrawable kbd = rotBtn.getImageDrawable();
522         if (kbd == null) return;
523 
524         // The KBD and AVD is recreated every new valid suggestion because of style changes.
525         AnimatedVectorDrawable animIcon = null;
526         if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
527             animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
528         }
529 
530         // Clear any pending suggestion flag as it has either been nullified or is being shown
531         mPendingRotationSuggestion = false;
532         if (getView() != null) getView().removeCallbacks(mCancelPendingRotationProposal);
533 
534         // Handle the visibility change and animation
535         if (visible) { // Appear and change (cannot force)
536             // Stop and clear any currently running hide animations
537             if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
538                 mRotateHideAnimator.cancel();
539             }
540             mRotateHideAnimator = null;
541 
542             // Reset the alpha if any has changed due to hide animation
543             view.setAlpha(1f);
544 
545             // Run the rotate icon's animation if it has one
546             if (animIcon != null) {
547                 animIcon.reset();
548                 animIcon.start();
549             }
550 
551             if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
552 
553             // Set visibility, may fail if a11y service is active.
554             // If invisible, call will stop animation.
555             int appliedVisibility = mNavigationBarView.setRotateButtonVisibility(true);
556             if (appliedVisibility == View.VISIBLE) {
557                 // If the button will actually become visible and the navbar is about to hide,
558                 // tell the statusbar to keep it around for longer
559                 mStatusBar.touchAutoHide();
560             }
561 
562         } else { // Hide
563 
564             mViewRippler.stop(); // Prevent any pending ripples, force hide or not
565 
566             if (force) {
567                 // If a hide animator is running stop it and make invisible
568                 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
569                     mRotateHideAnimator.pause();
570                 }
571                 mNavigationBarView.setRotateButtonVisibility(false);
572                 return;
573             }
574 
575             // Don't start any new hide animations if one is running
576             if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
577 
578             ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha",
579                     0f);
580             fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
581             fadeOut.setInterpolator(Interpolators.LINEAR);
582             fadeOut.addListener(new AnimatorListenerAdapter() {
583                 @Override
584                 public void onAnimationEnd(Animator animation) {
585                     mNavigationBarView.setRotateButtonVisibility(false);
586                 }
587             });
588 
589             mRotateHideAnimator = fadeOut;
590             fadeOut.start();
591         }
592     }
593 
rescheduleRotationTimeout(final boolean reasonHover)594     private void rescheduleRotationTimeout(final boolean reasonHover) {
595         // May be called due to a new rotation proposal or a change in hover state
596         if (reasonHover) {
597             // Don't reschedule if a hide animator is running
598             if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
599             // Don't reschedule if not visible
600             if (!mNavigationBarView.isRotateButtonVisible()) return;
601         }
602 
603         getView().removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
604         getView().postDelayed(mRemoveRotationProposal,
605                 computeRotationProposalTimeout()); // Schedule timeout
606     }
607 
computeRotationProposalTimeout()608     private int computeRotationProposalTimeout() {
609         if (mAccessibilityFeedbackEnabled) return 20000;
610         if (mHoveringRotationSuggestion) return 16000;
611         return 10000;
612     }
613 
isRotateSuggestionIntroduced()614     private boolean isRotateSuggestionIntroduced() {
615         ContentResolver cr = getContext().getContentResolver();
616         return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
617                 >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
618     }
619 
incrementNumAcceptedRotationSuggestionsIfNeeded()620     private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
621         // Get the number of accepted suggestions
622         ContentResolver cr = getContext().getContentResolver();
623         final int numSuggestions = Settings.Secure.getInt(cr,
624                 Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
625 
626         // Increment the number of accepted suggestions only if it would change intro mode
627         if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
628             Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
629                     numSuggestions + 1);
630         }
631     }
632 
633     // Injected from StatusBar at creation.
setCurrentSysuiVisibility(int systemUiVisibility)634     public void setCurrentSysuiVisibility(int systemUiVisibility) {
635         mSystemUiVisibility = systemUiVisibility;
636         mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
637                 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
638                 View.NAVIGATION_BAR_TRANSPARENT);
639         checkNavBarModes();
640         mStatusBar.touchAutoHide();
641         mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
642                 true /* nbModeChanged */, mNavigationBarMode);
643     }
644 
645     @Override
setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds)646     public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
647             int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
648         final int oldVal = mSystemUiVisibility;
649         final int newVal = (oldVal & ~mask) | (vis & mask);
650         final int diff = newVal ^ oldVal;
651         boolean nbModeChanged = false;
652         if (diff != 0) {
653             mSystemUiVisibility = newVal;
654 
655             // update navigation bar mode
656             final int nbMode = getView() == null
657                     ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
658                     View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
659                     View.NAVIGATION_BAR_TRANSPARENT);
660             nbModeChanged = nbMode != -1;
661             if (nbModeChanged) {
662                 if (mNavigationBarMode != nbMode) {
663                     mNavigationBarMode = nbMode;
664                     checkNavBarModes();
665                 }
666                 mStatusBar.touchAutoHide();
667             }
668         }
669 
670         mLightBarController.onNavigationVisibilityChanged(vis, mask, nbModeChanged,
671                 mNavigationBarMode);
672     }
673 
674     @Override
disable(int state1, int state2, boolean animate)675     public void disable(int state1, int state2, boolean animate) {
676         // Navigation bar flags are in both state1 and state2.
677         final int masked = state1 & (StatusBarManager.DISABLE_HOME
678                 | StatusBarManager.DISABLE_RECENT
679                 | StatusBarManager.DISABLE_BACK
680                 | StatusBarManager.DISABLE_SEARCH);
681         if (masked != mDisabledFlags1) {
682             mDisabledFlags1 = masked;
683             if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
684             updateScreenPinningGestures();
685         }
686 
687         final int masked2 = state2 & (StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
688         if (masked2 != mDisabledFlags2) {
689             mDisabledFlags2 = masked2;
690             setDisabled2Flags(masked2);
691         }
692     }
693 
setDisabled2Flags(int state2)694     private void setDisabled2Flags(int state2) {
695         // Method only called on change of disable2 flags
696         final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
697         if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
698     }
699 
hasDisable2RotateSuggestionFlag(int disable2Flags)700     private boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
701         return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
702     }
703 
704     // ----- Internal stuffz -----
705 
refreshLayout(int layoutDirection)706     private void refreshLayout(int layoutDirection) {
707         if (mNavigationBarView != null) {
708             mNavigationBarView.setLayoutDirection(layoutDirection);
709         }
710     }
711 
shouldDisableNavbarGestures()712     private boolean shouldDisableNavbarGestures() {
713         return !mStatusBar.isDeviceProvisioned()
714                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
715     }
716 
repositionNavigationBar()717     private void repositionNavigationBar() {
718         if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
719 
720         prepareNavigationBarView();
721 
722         mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
723                 ((View) mNavigationBarView.getParent()).getLayoutParams());
724     }
725 
updateScreenPinningGestures()726     private void updateScreenPinningGestures() {
727         if (mNavigationBarView == null) {
728             return;
729         }
730 
731         // Change the cancel pin gesture to home and back if recents button is invisible
732         boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible();
733         ButtonDispatcher backButton = mNavigationBarView.getBackButton();
734         if (recentsVisible) {
735             backButton.setOnLongClickListener(this::onLongPressBackRecents);
736         } else {
737             backButton.setOnLongClickListener(this::onLongPressBackHome);
738         }
739     }
740 
notifyNavigationBarScreenOn()741     private void notifyNavigationBarScreenOn() {
742         mNavigationBarView.updateNavButtonIcons();
743     }
744 
prepareNavigationBarView()745     private void prepareNavigationBarView() {
746         mNavigationBarView.reorient();
747 
748         ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
749         recentsButton.setOnClickListener(this::onRecentsClick);
750         recentsButton.setOnTouchListener(this::onRecentsTouch);
751         recentsButton.setLongClickable(true);
752         recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
753 
754         ButtonDispatcher backButton = mNavigationBarView.getBackButton();
755         backButton.setLongClickable(true);
756 
757         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
758         homeButton.setOnTouchListener(this::onHomeTouch);
759         homeButton.setOnLongClickListener(this::onHomeLongClick);
760 
761         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
762         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
763         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
764         updateAccessibilityServicesState(mAccessibilityManager);
765 
766         ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
767         rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
768         rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover);
769         updateScreenPinningGestures();
770     }
771 
onHomeTouch(View v, MotionEvent event)772     private boolean onHomeTouch(View v, MotionEvent event) {
773         if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
774             return true;
775         }
776         // If an incoming call is ringing, HOME is totally disabled.
777         // (The user is already on the InCallUI at this point,
778         // and his ONLY options are to answer or reject the call.)
779         switch (event.getAction()) {
780             case MotionEvent.ACTION_DOWN:
781                 mHomeBlockedThisTouch = false;
782                 TelecomManager telecomManager =
783                         getContext().getSystemService(TelecomManager.class);
784                 if (telecomManager != null && telecomManager.isRinging()) {
785                     if (mStatusBar.isKeyguardShowing()) {
786                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
787                                 "No heads up");
788                         mHomeBlockedThisTouch = true;
789                         return true;
790                     }
791                 }
792                 break;
793             case MotionEvent.ACTION_UP:
794             case MotionEvent.ACTION_CANCEL:
795                 mStatusBar.awakenDreams();
796                 break;
797         }
798         return false;
799     }
800 
onVerticalChanged(boolean isVertical)801     private void onVerticalChanged(boolean isVertical) {
802         mStatusBar.setQsScrimEnabled(!isVertical);
803     }
804 
onNavigationTouch(View v, MotionEvent event)805     private boolean onNavigationTouch(View v, MotionEvent event) {
806         mStatusBar.checkUserAutohide(event);
807         return false;
808     }
809 
810     @VisibleForTesting
onHomeLongClick(View v)811     boolean onHomeLongClick(View v) {
812         if (!mNavigationBarView.isRecentsButtonVisible()
813                 && ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
814             return onLongPressBackHome(v);
815         }
816         if (shouldDisableNavbarGestures()) {
817             return false;
818         }
819         mNavigationBarView.onNavigationButtonLongPress(v);
820         mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
821         mAssistManager.startAssist(new Bundle() /* args */);
822         mStatusBar.awakenDreams();
823 
824         if (mNavigationBarView != null) {
825             mNavigationBarView.abortCurrentGesture();
826         }
827         return true;
828     }
829 
830     // additional optimization when we have software system buttons - start loading the recent
831     // tasks on touch down
onRecentsTouch(View v, MotionEvent event)832     private boolean onRecentsTouch(View v, MotionEvent event) {
833         int action = event.getAction() & MotionEvent.ACTION_MASK;
834         if (action == MotionEvent.ACTION_DOWN) {
835             mCommandQueue.preloadRecentApps();
836         } else if (action == MotionEvent.ACTION_CANCEL) {
837             mCommandQueue.cancelPreloadRecentApps();
838         } else if (action == MotionEvent.ACTION_UP) {
839             if (!v.isPressed()) {
840                 mCommandQueue.cancelPreloadRecentApps();
841             }
842         }
843         return false;
844     }
845 
onRecentsClick(View v)846     private void onRecentsClick(View v) {
847         if (LatencyTracker.isEnabled(getContext())) {
848             LatencyTracker.getInstance(getContext()).onActionStart(
849                     LatencyTracker.ACTION_TOGGLE_RECENTS);
850         }
851         mStatusBar.awakenDreams();
852         mCommandQueue.toggleRecentApps();
853     }
854 
onLongPressBackHome(View v)855     private boolean onLongPressBackHome(View v) {
856         mNavigationBarView.onNavigationButtonLongPress(v);
857         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
858     }
859 
onLongPressBackRecents(View v)860     private boolean onLongPressBackRecents(View v) {
861         mNavigationBarView.onNavigationButtonLongPress(v);
862         return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
863     }
864 
865     /**
866      * This handles long-press of both back and recents/home. Back is the common button with
867      * combination of recents if it is visible or home if recents is invisible.
868      * They are handled together to capture them both being long-pressed
869      * at the same time to exit screen pinning (lock task).
870      *
871      * When accessibility mode is on, only a long-press from recents/home
872      * is required to exit.
873      *
874      * In all other circumstances we try to pass through long-press events
875      * for Back, so that apps can still use it.  Which can be from two things.
876      * 1) Not currently in screen pinning (lock task).
877      * 2) Back is long-pressed without recents/home.
878      */
onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2)879     private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) {
880         try {
881             boolean sendBackLongPress = false;
882             IActivityManager activityManager = ActivityManagerNative.getDefault();
883             boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
884             boolean inLockTaskMode = activityManager.isInLockTaskMode();
885             if (inLockTaskMode && !touchExplorationEnabled) {
886                 long time = System.currentTimeMillis();
887 
888                 // If we recently long-pressed the other button then they were
889                 // long-pressed 'together'
890                 if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
891                     activityManager.stopSystemLockTaskMode();
892                     // When exiting refresh disabled flags.
893                     mNavigationBarView.updateNavButtonIcons();
894                     return true;
895                 } else if (v.getId() == btnId1) {
896                     ButtonDispatcher button = btnId2 == R.id.recent_apps
897                             ? mNavigationBarView.getRecentsButton()
898                             : mNavigationBarView.getHomeButton();
899                     if (!button.getCurrentView().isPressed()) {
900                         // If we aren't pressing recents/home right now then they presses
901                         // won't be together, so send the standard long-press action.
902                         sendBackLongPress = true;
903                     }
904                 }
905                 mLastLockToAppLongPress = time;
906             } else {
907                 // If this is back still need to handle sending the long-press event.
908                 if (v.getId() == btnId1) {
909                     sendBackLongPress = true;
910                 } else if (touchExplorationEnabled && inLockTaskMode) {
911                     // When in accessibility mode a long press that is recents/home (not back)
912                     // should stop lock task.
913                     activityManager.stopSystemLockTaskMode();
914                     // When exiting refresh disabled flags.
915                     mNavigationBarView.updateNavButtonIcons();
916                     return true;
917                 } else if (v.getId() == btnId2) {
918                     return btnId2 == R.id.recent_apps
919                             ? onLongPressRecents()
920                             : onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView());
921                 }
922             }
923             if (sendBackLongPress) {
924                 KeyButtonView keyButtonView = (KeyButtonView) v;
925                 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
926                 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
927                 return true;
928             }
929         } catch (RemoteException e) {
930             Log.d(TAG, "Unable to reach activity manager", e);
931         }
932         return false;
933     }
934 
onLongPressRecents()935     private boolean onLongPressRecents() {
936         if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext())
937                 || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
938                 || Recents.getConfiguration().isLowRamDevice
939                 // If we are connected to the overview service, then disable the recents button
940                 || mOverviewProxyService.getProxy() != null) {
941             return false;
942         }
943 
944         return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
945                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
946     }
947 
onAccessibilityClick(View v)948     private void onAccessibilityClick(View v) {
949         mAccessibilityManager.notifyAccessibilityButtonClicked();
950     }
951 
onAccessibilityLongClick(View v)952     private boolean onAccessibilityLongClick(View v) {
953         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
954         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
955         v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
956         return true;
957     }
958 
updateAccessibilityServicesState(AccessibilityManager accessibilityManager)959     private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
960         int requestingServices = 0;
961         try {
962             if (Settings.Secure.getIntForUser(mContentResolver,
963                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
964                     UserHandle.USER_CURRENT) == 1) {
965                 requestingServices++;
966             }
967         } catch (Settings.SettingNotFoundException e) {
968         }
969 
970         boolean feedbackEnabled = false;
971         // AccessibilityManagerService resolves services for the current user since the local
972         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
973         final List<AccessibilityServiceInfo> services =
974                 accessibilityManager.getEnabledAccessibilityServiceList(
975                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
976         for (int i = services.size() - 1; i >= 0; --i) {
977             AccessibilityServiceInfo info = services.get(i);
978             if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
979                 requestingServices++;
980             }
981 
982             if (info.feedbackType != 0 && info.feedbackType !=
983                     AccessibilityServiceInfo.FEEDBACK_GENERIC) {
984                 feedbackEnabled = true;
985             }
986         }
987 
988         mAccessibilityFeedbackEnabled = feedbackEnabled;
989 
990         final boolean showAccessibilityButton = requestingServices >= 1;
991         final boolean targetSelection = requestingServices >= 2;
992         mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
993     }
994 
onRotateSuggestionClick(View v)995     private void onRotateSuggestionClick(View v) {
996         mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
997         incrementNumAcceptedRotationSuggestionsIfNeeded();
998         mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
999     }
1000 
onRotateSuggestionHover(View v, MotionEvent event)1001     private boolean onRotateSuggestionHover(View v, MotionEvent event) {
1002         final int action = event.getActionMasked();
1003         mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
1004                 || (action == MotionEvent.ACTION_HOVER_MOVE);
1005         rescheduleRotationTimeout(true);
1006         return false; // Must return false so a11y hover events are dispatched correctly.
1007     }
1008 
1009     // ----- Methods that StatusBar talks to (should be minimized) -----
1010 
setLightBarController(LightBarController lightBarController)1011     public void setLightBarController(LightBarController lightBarController) {
1012         mLightBarController = lightBarController;
1013         mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
1014     }
1015 
isSemiTransparent()1016     public boolean isSemiTransparent() {
1017         return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
1018     }
1019 
disableAnimationsDuringHide(long delay)1020     public void disableAnimationsDuringHide(long delay) {
1021         mNavigationBarView.setLayoutTransitionsEnabled(false);
1022         mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
1023                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
1024     }
1025 
getBarTransitions()1026     public BarTransitions getBarTransitions() {
1027         return mNavigationBarView.getBarTransitions();
1028     }
1029 
checkNavBarModes()1030     public void checkNavBarModes() {
1031         mStatusBar.checkBarMode(mNavigationBarMode,
1032                 mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
1033     }
1034 
finishBarAnimations()1035     public void finishBarAnimations() {
1036         mNavigationBarView.getBarTransitions().finishAnimations();
1037     }
1038 
1039     private final AccessibilityServicesStateChangeListener mAccessibilityListener =
1040             this::updateAccessibilityServicesState;
1041 
1042     private class MagnificationContentObserver extends ContentObserver {
1043 
MagnificationContentObserver(Handler handler)1044         public MagnificationContentObserver(Handler handler) {
1045             super(handler);
1046         }
1047 
1048         @Override
onChange(boolean selfChange)1049         public void onChange(boolean selfChange) {
1050             NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
1051         }
1052     }
1053 
1054     private final Stub mRotationWatcher = new Stub() {
1055         @Override
1056         public void onRotationChanged(final int rotation) throws RemoteException {
1057             // We need this to be scheduled as early as possible to beat the redrawing of
1058             // window in response to the orientation change.
1059             Handler h = getView().getHandler();
1060             Message msg = Message.obtain(h, () -> {
1061 
1062                 // If the screen rotation changes while locked, potentially update lock to flow with
1063                 // new screen rotation and hide any showing suggestions.
1064                 if (mRotationLockController.isRotationLocked()) {
1065                     if (shouldOverrideUserLockPrefs(rotation)) {
1066                         mRotationLockController.setRotationLockedAtAngle(true, rotation);
1067                     }
1068                     setRotateSuggestionButtonState(false, true);
1069                 }
1070 
1071                 if (mNavigationBarView != null
1072                         && mNavigationBarView.needsReorient(rotation)) {
1073                     repositionNavigationBar();
1074                 }
1075             });
1076             msg.setAsynchronous(true);
1077             h.sendMessageAtFrontOfQueue(msg);
1078         }
1079 
1080         private boolean shouldOverrideUserLockPrefs(final int rotation) {
1081             // Only override user prefs when returning to the natural rotation (normally portrait).
1082             // Don't let apps that force landscape or 180 alter user lock.
1083             return rotation == NATURAL_ROTATION;
1084         }
1085     };
1086 
1087     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1088         @Override
1089         public void onReceive(Context context, Intent intent) {
1090             String action = intent.getAction();
1091             if (Intent.ACTION_SCREEN_OFF.equals(action)
1092                     || Intent.ACTION_SCREEN_ON.equals(action)) {
1093                 notifyNavigationBarScreenOn();
1094             }
1095             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
1096                 // The accessibility settings may be different for the new user
1097                 updateAccessibilityServicesState(mAccessibilityManager);
1098             };
1099         }
1100     };
1101 
1102     class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
1103         // Invalidate any rotation suggestion on task change or activity orientation change
1104         // Note: all callbacks happen on main thread
1105 
1106         @Override
onTaskStackChanged()1107         public void onTaskStackChanged() {
1108             setRotateSuggestionButtonState(false);
1109         }
1110 
1111         @Override
onTaskRemoved(int taskId)1112         public void onTaskRemoved(int taskId) {
1113             setRotateSuggestionButtonState(false);
1114         }
1115 
1116         @Override
onTaskMovedToFront(int taskId)1117         public void onTaskMovedToFront(int taskId) {
1118             setRotateSuggestionButtonState(false);
1119         }
1120 
1121         @Override
onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)1122         public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
1123             // Only hide the icon if the top task changes its requestedOrientation
1124             // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
1125             Optional.ofNullable(ActivityManagerWrapper.getInstance())
1126                     .map(ActivityManagerWrapper::getRunningTask)
1127                     .ifPresent(a -> {
1128                         if (a.id == taskId) setRotateSuggestionButtonState(false);
1129                     });
1130         }
1131     }
1132 
1133     private class ViewRippler {
1134         private static final int RIPPLE_OFFSET_MS = 50;
1135         private static final int RIPPLE_INTERVAL_MS = 2000;
1136         private View mRoot;
1137 
start(View root)1138         public void start(View root) {
1139             stop(); // Stop any pending ripple animations
1140 
1141             mRoot = root;
1142 
1143             // Schedule pending ripples, offset the 1st to avoid problems with visibility change
1144             mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
1145             mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
1146             mRoot.postOnAnimationDelayed(mRipple, 2*RIPPLE_INTERVAL_MS);
1147             mRoot.postOnAnimationDelayed(mRipple, 3*RIPPLE_INTERVAL_MS);
1148             mRoot.postOnAnimationDelayed(mRipple, 4*RIPPLE_INTERVAL_MS);
1149         }
1150 
stop()1151         public void stop() {
1152             if (mRoot != null) mRoot.removeCallbacks(mRipple);
1153         }
1154 
1155         private final Runnable mRipple = new Runnable() {
1156             @Override
1157             public void run() { // Cause the ripple to fire via false presses
1158                 if (!mRoot.isAttachedToWindow()) return;
1159                 mRoot.setPressed(true);
1160                 mRoot.setPressed(false);
1161             }
1162         };
1163     }
1164 
create(Context context, FragmentListener listener)1165     public static View create(Context context, FragmentListener listener) {
1166         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1167                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
1168                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
1169                 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
1170                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1171                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1172                         | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1173                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
1174                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
1175                 PixelFormat.TRANSLUCENT);
1176         lp.token = new Binder();
1177         lp.setTitle("NavigationBar");
1178         lp.accessibilityTitle = context.getString(R.string.nav_bar);
1179         lp.windowAnimations = 0;
1180 
1181         View navigationBarView = LayoutInflater.from(context).inflate(
1182                 R.layout.navigation_bar_window, null);
1183 
1184         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
1185         if (navigationBarView == null) return null;
1186 
1187         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
1188         FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
1189         NavigationBarFragment fragment = new NavigationBarFragment();
1190         fragmentHost.getFragmentManager().beginTransaction()
1191                 .replace(R.id.navigation_bar_frame, fragment, TAG)
1192                 .commit();
1193         fragmentHost.addTagListener(TAG, listener);
1194         return navigationBarView;
1195     }
1196 }
1197