Home | History | Annotate | Download | only in phone
      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
    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
    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
    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
    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
    298     public void onDestroyView() {
    299         super.onDestroyView();
    300         mNavigationBarView.getLightTransitionsController().destroy(getContext());
    301         mOverviewProxyService.removeCallback(mOverviewProxyListener);
    302         getContext().unregisterReceiver(mBroadcastReceiver);
    303     }
    304 
    305     @Override
    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
    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
    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
    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
    388     public void topAppWindowChanged(boolean showMenu) {
    389         if (mNavigationBarView != null) {
    390             mNavigationBarView.setMenuVisibility(showMenu);
    391         }
    392     }
    393 
    394     @Override
    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
    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 
    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 
    478     private void showAndLogRotationSuggestion() {
    479         setRotateSuggestionButtonState(true);
    480         rescheduleRotationTimeout(false);
    481         mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
    482     }
    483 
    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 
    501     public void setRotateSuggestionButtonState(final boolean visible) {
    502         setRotateSuggestionButtonState(visible, false);
    503     }
    504 
    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 
    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 
    608     private int computeRotationProposalTimeout() {
    609         if (mAccessibilityFeedbackEnabled) return 20000;
    610         if (mHoveringRotationSuggestion) return 16000;
    611         return 10000;
    612     }
    613 
    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 
    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.
    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
    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
    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 
    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 
    700     private boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
    701         return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
    702     }
    703 
    704     // ----- Internal stuffz -----
    705 
    706     private void refreshLayout(int layoutDirection) {
    707         if (mNavigationBarView != null) {
    708             mNavigationBarView.setLayoutDirection(layoutDirection);
    709         }
    710     }
    711 
    712     private boolean shouldDisableNavbarGestures() {
    713         return !mStatusBar.isDeviceProvisioned()
    714                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
    715     }
    716 
    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 
    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 
    741     private void notifyNavigationBarScreenOn() {
    742         mNavigationBarView.updateNavButtonIcons();
    743     }
    744 
    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 
    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 
    801     private void onVerticalChanged(boolean isVertical) {
    802         mStatusBar.setQsScrimEnabled(!isVertical);
    803     }
    804 
    805     private boolean onNavigationTouch(View v, MotionEvent event) {
    806         mStatusBar.checkUserAutohide(event);
    807         return false;
    808     }
    809 
    810     @VisibleForTesting
    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
    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 
    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 
    855     private boolean onLongPressBackHome(View v) {
    856         mNavigationBarView.onNavigationButtonLongPress(v);
    857         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
    858     }
    859 
    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      */
    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 
    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 
    948     private void onAccessibilityClick(View v) {
    949         mAccessibilityManager.notifyAccessibilityButtonClicked();
    950     }
    951 
    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 
    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 
    995     private void onRotateSuggestionClick(View v) {
    996         mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
    997         incrementNumAcceptedRotationSuggestionsIfNeeded();
    998         mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
    999     }
   1000 
   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 
   1011     public void setLightBarController(LightBarController lightBarController) {
   1012         mLightBarController = lightBarController;
   1013         mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
   1014     }
   1015 
   1016     public boolean isSemiTransparent() {
   1017         return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
   1018     }
   1019 
   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 
   1026     public BarTransitions getBarTransitions() {
   1027         return mNavigationBarView.getBarTransitions();
   1028     }
   1029 
   1030     public void checkNavBarModes() {
   1031         mStatusBar.checkBarMode(mNavigationBarMode,
   1032                 mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
   1033     }
   1034 
   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 
   1044         public MagnificationContentObserver(Handler handler) {
   1045             super(handler);
   1046         }
   1047 
   1048         @Override
   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
   1107         public void onTaskStackChanged() {
   1108             setRotateSuggestionButtonState(false);
   1109         }
   1110 
   1111         @Override
   1112         public void onTaskRemoved(int taskId) {
   1113             setRotateSuggestionButtonState(false);
   1114         }
   1115 
   1116         @Override
   1117         public void onTaskMovedToFront(int taskId) {
   1118             setRotateSuggestionButtonState(false);
   1119         }
   1120 
   1121         @Override
   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 
   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 
   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 
   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