13/ Rely on the device state's navigation mode

- Rely on the device state navigation mode instead of tracking it
  independently in various places

Bug: 141886704
Change-Id: I421c1fa11ca7362aff8e2388a2b5d427b39af3e9
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 939656e..2755492 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -96,6 +96,7 @@
     protected float mDragLengthFactor = 1;
 
     protected final Context mContext;
+    protected final RecentsAnimationDeviceState mDeviceState;
     protected final GestureState mGestureState;
     protected final OverviewComponentObserver mOverviewComponentObserver;
     protected final BaseActivityInterface<T> mActivityInterface;
@@ -106,7 +107,6 @@
     protected final TransformParams mTransformParams = new TransformParams();
 
     private final Vibrator mVibrator;
-    protected final Mode mMode;
 
     // Shift in the range of [0, 1].
     // 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -135,10 +135,11 @@
     protected boolean mCanceled;
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
-    protected BaseSwipeUpHandler(Context context, GestureState gestureState,
-            OverviewComponentObserver overviewComponentObserver,
+    protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, OverviewComponentObserver overviewComponentObserver,
             RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
         mContext = context;
+        mDeviceState = deviceState;
         mGestureState = gestureState;
         mOverviewComponentObserver = overviewComponentObserver;
         mActivityInterface = gestureState.getActivityInterface();
@@ -147,7 +148,6 @@
                 mActivityInterface.createActivityInitListener(this::onActivityInit);
         mRunningTaskId = runningTaskId;
         mInputConsumer = inputConsumer;
-        mMode = SysUINavigationMode.getMode(context);
 
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
@@ -348,7 +348,7 @@
             mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
         }
         mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
-        if (mMode == Mode.NO_BUTTON) {
+        if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
             mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 8d7a534..b4df81a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -63,8 +63,6 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
@@ -114,8 +112,7 @@
  * Service connected by system-UI for handling touch interaction.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class TouchInteractionService extends Service implements
-        NavigationModeChangeListener, PluginListener<OverscrollPlugin> {
+public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin> {
 
     private static final String TAG = "TouchInteractionService";
 
@@ -269,20 +266,18 @@
 
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
-    private Mode mMode = Mode.THREE_BUTTONS;
 
     @Override
     public void onCreate() {
         super.onCreate();
-        mDeviceState = new RecentsAnimationDeviceState(this);
-        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
-
         // Initialize anything here that is needed in direct boot mode.
         // Everything else should be initialized in onUserUnlocked() below.
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
+        mDeviceState = new RecentsAnimationDeviceState(this);
+        mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
 
-        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this));
         sConnected = true;
 
         PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this,
@@ -308,7 +303,7 @@
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
         }
         disposeEventHandlers();
-        if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) {
+        if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
             return;
         }
         if (TestProtocol.sDebugTracing) {
@@ -327,12 +322,10 @@
         mDeviceState.updateGestureTouchRegions();
     }
 
-    @Override
-    public void onNavigationModeChanged(Mode newMode) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
-        }
-        mMode = newMode;
+    /**
+     * Called when the navigation mode changes, guaranteed to be after the device state has updated.
+     */
+    private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
         initInputMonitor();
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
@@ -369,7 +362,7 @@
     }
 
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
-        if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) {
+        if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
             // mode doesn't have gestures
             return;
@@ -417,7 +410,6 @@
         }
         disposeEventHandlers();
         mDeviceState.destroy();
-        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
         SystemUiProxy.INSTANCE.get(this).setProxy(null);
 
         sConnected = false;
@@ -453,7 +445,8 @@
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
-            } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON
+            } else if (mDeviceState.isUserUnlocked()
+                    && mDeviceState.isFullyGesturalNavMode()
                     && mDeviceState.canTriggerAssistantAction(event)) {
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
@@ -494,7 +487,7 @@
                 || previousGestureState.isRecentsAnimationRunning()
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
-        if (mMode == Mode.NO_BUTTON) {
+        if (mDeviceState.isFullyGesturalNavMode()) {
             if (mDeviceState.canTriggerAssistantAction(event)) {
                 base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
             }
@@ -584,7 +577,8 @@
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
 
-        if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+        if (mDeviceState.isFullyGesturalNavMode()
+                && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
             shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
             factory = mFallbackNoButtonFactory;
         } else {
@@ -601,7 +595,7 @@
 
     private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
             RunningTaskInfo taskInfo) {
-        if (mMode == Mode.NO_BUTTON && taskInfo != null) {
+        if (mDeviceState.isFullyGesturalNavMode() && taskInfo != null) {
             return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
                     gestureState, mInputMonitorCompat, taskInfo.taskId);
         } else {
@@ -641,7 +635,7 @@
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
-        if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+        if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
             // Prevent the overview from being started before the real home on first boot.
             return;
         }
@@ -708,7 +702,6 @@
             // Dump everything
             mDeviceState.dump(pw);
             pw.println("TouchState:");
-            pw.println("  navMode=" + mMode);
             boolean resumed = mOverviewComponentObserver != null
                     && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
@@ -748,9 +741,9 @@
     private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
             RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
             boolean isLikelyToStartNewTask) {
-        return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver,
-                runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask,
-                continuingLastGesture);
+        return new FallbackNoButtonInputConsumer(this, mDeviceState, gestureState,
+                mOverviewComponentObserver, runningTask, mRecentsModel, mInputConsumer,
+                isLikelyToStartNewTask, continuingLastGesture);
     }
 
     protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f1b3598..8e9c898 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -157,9 +157,7 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private final RecentsAnimationDeviceState mDeviceState;
     private final TaskAnimationManager mTaskAnimationManager;
-    private final GestureState mGestureState;
 
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
@@ -198,11 +196,9 @@
             RunningTaskInfo runningTaskInfo, long touchTimeMs,
             OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture,
             InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
-                runningTaskInfo.id);
-        mDeviceState = deviceState;
+        super(context, deviceState, gestureState, overviewComponentObserver, recentsModel,
+                inputConsumer, runningTaskInfo.id);
         mTaskAnimationManager = taskAnimationManager;
-        mGestureState = gestureState;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
         initStateCallbacks();
@@ -444,7 +440,7 @@
      * Note this method has no effect unless the navigation mode is NO_BUTTON.
      */
     private void maybeUpdateRecentsAttachedState(boolean animate) {
-        if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
+        if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
             return;
         }
         RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null
@@ -546,7 +542,7 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (mMode != Mode.NO_BUTTON) {
+            if (!mDeviceState.isFullyGesturalNavMode()) {
                 performHapticFeedback();
             }
         }
@@ -730,7 +726,7 @@
         if (!isFling) {
             if (isCancel) {
                 endTarget = LAST_TASK;
-            } else if (mMode == Mode.NO_BUTTON) {
+            } else if (mDeviceState.isFullyGesturalNavMode()) {
                 if (mIsShelfPeeking) {
                     endTarget = RECENTS;
                 } else if (goingToNewTask) {
@@ -751,9 +747,9 @@
             boolean willGoToNewTaskOnSwipeUp =
                     goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
 
-            if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
+            if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
                 endTarget = HOME;
-            } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) {
+            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
                 // If swiping at a diagonal, base end target on the faster velocity.
                 endTarget = NEW_TASK;
             } else if (isSwipeUp) {
@@ -793,7 +789,7 @@
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
-                if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
+                if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
                             startShift, endShift, endShift, endVelocity / 1000,
                             mTransitionDragLength, mContext);
@@ -839,7 +835,7 @@
                 }
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
-            if (mMode == Mode.NO_BUTTON) {
+            if (mDeviceState.isFullyGesturalNavMode()) {
                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
             }
         } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
index 152b9c9..7b24bd9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -50,6 +50,7 @@
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -112,13 +113,13 @@
     private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
     private RunningWindowAnim mFinishAnimation;
 
-    public FallbackNoButtonInputConsumer(Context context, GestureState gestureState,
-            OverviewComponentObserver overviewComponentObserver,
+    public FallbackNoButtonInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, OverviewComponentObserver overviewComponentObserver,
             RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
             InputConsumerController inputConsumer,
             boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
-        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
-                runningTaskInfo.id);
+        super(context, deviceState, gestureState, overviewComponentObserver, recentsModel,
+                inputConsumer, runningTaskInfo.id);
         mLauncherAlpha.value = 1;
 
         mRunningTaskInfo = runningTaskInfo;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index c479250..aeab4b5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -54,8 +54,6 @@
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
@@ -85,7 +83,6 @@
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
     private final InputMonitorCompat mInputMonitorCompat;
-    private final SysUINavigationMode.Mode mMode;
     private final BaseActivityInterface mActivityInterface;
 
     private final BaseSwipeUpHandler.Factory mHandlerFactory;
@@ -137,7 +134,6 @@
         mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
-        mMode = SysUINavigationMode.getMode(base);
         mHandlerFactory = handlerFactory;
         mActivityInterface = mGestureState.getActivityInterface();
 
@@ -293,7 +289,7 @@
                         mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                     }
 
-                    if (mMode == Mode.NO_BUTTON) {
+                    if (mDeviceState.isFullyGesturalNavMode()) {
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index e6e3297..1855e64 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -43,6 +44,7 @@
 import android.graphics.Region;
 import android.os.Process;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.Surface;
 
@@ -52,7 +54,9 @@
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -65,7 +69,7 @@
  * Manages the state of the system during a swipe up gesture.
  */
 public class RecentsAnimationDeviceState implements
-        SysUINavigationMode.NavigationModeChangeListener,
+        NavigationModeChangeListener,
         DefaultDisplay.DisplayInfoChangeListener {
 
     private Context mContext;
@@ -74,6 +78,8 @@
     private DefaultDisplay mDefaultDisplay;
     private int mDisplayId;
 
+    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
+
     private @SystemUiStateFlags int mSystemUiStateFlags;
     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
 
@@ -114,6 +120,7 @@
             mContext.registerReceiver(mUserUnlockedReceiver,
                     new IntentFilter(ACTION_USER_UNLOCKED));
         }
+        runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver));
 
         // Register for exclusion updates
         mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
@@ -124,7 +131,11 @@
                 mExclusionRegion = region;
             }
         };
+        runOnDestroy(mExclusionListener::unregister);
+
+        // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+        runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
 
         // Add any blocked activities
         String blockingActivity = context.getString(R.string.gesture_blocking_activity);
@@ -133,18 +144,34 @@
         }
     }
 
+    private void runOnDestroy(Runnable action) {
+        mOnDestroyActions.add(action);
+    }
+
     /**
      * Cleans up all the registered listeners and receivers.
      */
     public void destroy() {
-        Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
-        mSysUiNavMode.removeModeChangeListener(this);
+        for (Runnable r : mOnDestroyActions) {
+            r.run();
+        }
         mDefaultDisplay.removeChangeListener(this);
-        mExclusionListener.unregister();
+    }
+
+    /**
+     * Adds a listener for the nav mode change, guaranteed to be called after the device state's
+     * mode has changed.
+     */
+    public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) {
+        listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener));
+        runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
     }
 
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
+        }
         mDefaultDisplay.removeChangeListener(this);
         if (newMode.hasGestures) {
             mDefaultDisplay.addChangeListener(this);
@@ -168,6 +195,34 @@
     }
 
     /**
+     * @return the current navigation mode for the device.
+     */
+    public SysUINavigationMode.Mode getNavMode() {
+        return mMode;
+    }
+
+    /**
+     * @return whether the current nav mode is fully gestural.
+     */
+    public boolean isFullyGesturalNavMode() {
+        return mMode == NO_BUTTON;
+    }
+
+    /**
+     * @return whether the current nav mode has some gestures (either 2 or 0 button mode).
+     */
+    public boolean isGesturalNavMode() {
+        return mMode == TWO_BUTTONS || mMode == NO_BUTTON;
+    }
+
+    /**
+     * @return whether the current nav mode is button-based.
+     */
+    public boolean isButtonNavMode() {
+        return mMode == THREE_BUTTONS;
+    }
+
+    /**
      * @return the display id for the display that Launcher is running on.
      */
     public int getDisplayId() {