Merge "Move connectivity test defaults to tests/common" into sc-dev
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 2ed44ec..ba3fc1e 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -909,7 +909,8 @@
             return false;
         }
         for (int i = 0; i < oldColors.size(); i++) {
-            if (oldColors.valueAt(i) != newColors.get(oldColors.keyAt(i))) {
+            if (oldColors.keyAt(i) != newColors.keyAt(i)
+                    || oldColors.valueAt(i) != newColors.valueAt(i)) {
                 return false;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 242f8f1..8dc05de9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -19,7 +19,6 @@
 import android.content.res.Configuration;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
 
 /**
  * Interface to engage one handed feature.
@@ -60,11 +59,6 @@
     void stopOneHanded(int uiEvent);
 
     /**
-     * Sets navigation 3 button mode enabled or disabled by users.
-     */
-    void setThreeButtonModeEnabled(boolean enabled);
-
-    /**
      * Sets one handed feature temporary locked in enabled or disabled state, this won't change
      * settings configuration.
      *
@@ -80,12 +74,6 @@
     void registerTransitionCallback(OneHandedTransitionCallback callback);
 
     /**
-     * Registers callback for one handed gesture, this gesture callback will be activated on
-     * 3 button navigation mode only
-     */
-    void registerGestureCallback(OneHandedGestureEventCallback callback);
-
-    /**
      * Receive onConfigurationChanged() events
      */
     void onConfigChanged(Configuration newConfig);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ce57638..e506542 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -41,7 +41,6 @@
 import android.provider.Settings;
 import android.util.Slog;
 import android.view.Surface;
-import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -59,7 +58,6 @@
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
 
 import java.io.PrintWriter;
 
@@ -101,7 +99,6 @@
     private final OneHandedImpl mImpl = new OneHandedImpl();
 
     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
-    private OneHandedGestureHandler mGestureHandler;
     private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
 
     /**
@@ -113,7 +110,6 @@
                     return;
                 }
                 mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
-                mGestureHandler.onRotateDisplay(mDisplayAreaOrganizer.getDisplayLayout());
             };
 
     private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -196,7 +192,7 @@
 
     private boolean isInitialized() {
         if (mDisplayAreaOrganizer == null || mDisplayController == null
-                || mGestureHandler == null || mOneHandedSettingsUtil == null) {
+                || mOneHandedSettingsUtil == null) {
             Slog.w(TAG, "Components may not initialized yet!");
             return false;
         }
@@ -226,8 +222,6 @@
                 new OneHandedAnimationController(context);
         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
                 mainExecutor);
-        OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
-                context, displayLayout, ViewConfiguration.get(context), mainExecutor);
         OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
                 new OneHandedBackgroundPanelOrganizer(context, displayLayout, mainExecutor);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
@@ -238,7 +232,7 @@
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
-                gestureHandler, settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
+                settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
                 oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
                 mainHandler);
     }
@@ -250,7 +244,6 @@
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
-            OneHandedGestureHandler gestureHandler,
             OneHandedSettingsUtil settingsUtil,
             OneHandedAccessibilityUtil oneHandedAccessibilityUtil,
             OneHandedTimeoutHandler timeoutHandler,
@@ -269,7 +262,6 @@
         mTouchHandler = touchHandler;
         mState = state;
         mTutorialHandler = tutorialHandler;
-        mGestureHandler = gestureHandler;
         mOverlayManager = overlayManager;
         mMainExecutor = mainExecutor;
         mMainHandler = mainHandler;
@@ -399,24 +391,15 @@
         mOneHandedUiEventLogger.writeEvent(uiEvent);
     }
 
-    private void setThreeButtonModeEnabled(boolean enabled) {
-        mGestureHandler.onThreeButtonModeEnabled(enabled);
-    }
-
     @VisibleForTesting
     void registerTransitionCallback(OneHandedTransitionCallback callback) {
         mDisplayAreaOrganizer.registerTransitionCallback(callback);
     }
 
-    private void registerGestureCallback(OneHandedGestureEventCallback callback) {
-        mGestureHandler.setGestureEventListener(callback);
-    }
-
     private void setupCallback() {
         mTouchHandler.registerTouchEventListener(() ->
                 stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_OVERSPACE_OUT));
         mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler);
-        mDisplayAreaOrganizer.registerTransitionCallback(mGestureHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mBackgroundPanelOrganizer);
         mDisplayAreaOrganizer.registerTransitionCallback(mTransitionCallBack);
@@ -465,7 +448,6 @@
     private void updateDisplayLayout(int displayId) {
         final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
         mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
-        mGestureHandler.onDisplayChanged(newDisplayLayout);
         mTutorialHandler.onDisplayChanged(newDisplayLayout);
     }
 
@@ -588,7 +570,6 @@
         }
 
         mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
-        mGestureHandler.onGestureEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
 
         if (!mIsOneHandedEnabled) {
             mDisplayAreaOrganizer.unregisterOrganizer();
@@ -643,9 +624,6 @@
             return;
         }
         mLockedDisabled = locked && !enabled;
-
-        // Disabled gesture when keyguard ON
-        mGestureHandler.onGestureEnabled(!mLockedDisabled && isFeatureEnabled);
     }
 
     private void onConfigChanged(Configuration newConfig) {
@@ -685,10 +663,6 @@
             mDisplayAreaOrganizer.dump(pw);
         }
 
-        if (mGestureHandler != null) {
-            mGestureHandler.dump(pw);
-        }
-
         if (mTouchHandler != null) {
             mTouchHandler.dump(pw);
         }
@@ -775,13 +749,6 @@
         }
 
         @Override
-        public void setThreeButtonModeEnabled(boolean enabled) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.setThreeButtonModeEnabled(enabled);
-            });
-        }
-
-        @Override
         public void setLockedDisabled(boolean locked, boolean enabled) {
             mMainExecutor.execute(() -> {
                 OneHandedController.this.setLockedDisabled(locked, enabled);
@@ -796,13 +763,6 @@
         }
 
         @Override
-        public void registerGestureCallback(OneHandedGestureEventCallback callback) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.registerGestureCallback(callback);
-            });
-        }
-
-        @Override
         public void onConfigChanged(Configuration newConfig) {
             mMainExecutor.execute(() -> {
                 OneHandedController.this.onConfigChanged(newConfig);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
deleted file mode 100644
index 0383229..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.onehanded;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.hardware.input.InputManager;
-import android.os.Looper;
-import android.view.Display;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputMonitor;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.ViewConfiguration;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.io.PrintWriter;
-
-/**
- * The class manage swipe up and down gesture for 3-Button mode navigation, others(e.g, 2-button,
- * full gesture mode) are handled by Launcher quick steps. TODO(b/160934654) Migrate to Launcher
- * quick steps
- */
-public class OneHandedGestureHandler implements OneHandedTransitionCallback {
-    private static final String TAG = "OneHandedGestureHandler";
-
-    private static final int ANGLE_MAX = 150;
-    private static final int ANGLE_MIN = 30;
-    private final float mDragDistThreshold;
-    private final float mSquaredSlop;
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-    private final PointF mStartDragPos = new PointF();
-
-    private boolean mPassedSlop;
-    private boolean mAllowGesture;
-    private boolean mIsEnabled;
-    private int mNavGestureHeight;
-    private boolean mIsThreeButtonModeEnabled;
-    private int mRotation = Surface.ROTATION_0;
-
-    @VisibleForTesting
-    InputMonitor mInputMonitor;
-    @VisibleForTesting
-    InputEventReceiver mInputEventReceiver;
-    private final ShellExecutor mMainExecutor;
-    @VisibleForTesting
-    @Nullable
-    OneHandedGestureEventCallback mGestureEventCallback;
-    private Rect mGestureRegion = new Rect();
-    private boolean mIsStopGesture;
-
-    /**
-     * Constructor of OneHandedGestureHandler, we only handle the gesture of {@link
-     * Display#DEFAULT_DISPLAY}
-     *
-     * @param context       Any context
-     * @param displayLayout Current {@link DisplayLayout} from controller
-     * @param viewConfig    {@link ViewConfiguration} to obtain touch slop
-     * @param mainExecutor  The wm-shell main executor
-     */
-    public OneHandedGestureHandler(Context context,
-            DisplayLayout displayLayout,
-            ViewConfiguration viewConfig,
-            ShellExecutor mainExecutor) {
-        mMainExecutor = mainExecutor;
-        mDragDistThreshold = context.getResources().getDimensionPixelSize(
-                R.dimen.gestures_onehanded_drag_threshold);
-
-        final float slop = viewConfig.getScaledTouchSlop();
-        mSquaredSlop = slop * slop;
-        onDisplayChanged(displayLayout);
-        updateIsEnabled();
-    }
-
-    /**
-     * Notifies by {@link OneHandedController}, when swipe down gesture is enabled on 3 button
-     * navigation bar mode.
-     *
-     * @param isEnabled Either one handed mode or swipe for notification function enabled or not
-     */
-    public void onGestureEnabled(boolean isEnabled) {
-        mIsEnabled = isEnabled;
-        updateIsEnabled();
-    }
-
-    void onThreeButtonModeEnabled(boolean isEnabled) {
-        mIsThreeButtonModeEnabled = isEnabled;
-        updateIsEnabled();
-    }
-
-    /**
-     * Registers {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
-     */
-    public void setGestureEventListener(OneHandedGestureEventCallback callback) {
-        mGestureEventCallback = callback;
-    }
-
-    /**
-     * Called when onDisplayAdded() or onDisplayRemoved() callback
-     * @param displayLayout The latest {@link DisplayLayout} representing current displayId
-     */
-    public void onDisplayChanged(DisplayLayout displayLayout) {
-        mNavGestureHeight = getNavBarSize(displayLayout);
-        mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
-                displayLayout.height());
-        mRotation = displayLayout.rotation();
-    }
-
-    private void onMotionEvent(MotionEvent ev) {
-        int action = ev.getActionMasked();
-        if (action == MotionEvent.ACTION_DOWN) {
-            mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) && isGestureAvailable();
-            if (mAllowGesture) {
-                mDownPos.set(ev.getX(), ev.getY());
-                mLastPos.set(mDownPos);
-            }
-        } else if (mAllowGesture) {
-            switch (action) {
-                case MotionEvent.ACTION_MOVE:
-                    mLastPos.set(ev.getX(), ev.getY());
-                    if (!mPassedSlop) {
-                        if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
-                                > mSquaredSlop) {
-                            mStartDragPos.set(mLastPos.x, mLastPos.y);
-                            if (isValidStartAngle(
-                                    mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)
-                                    || isValidExitAngle(
-                                    mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) {
-                                mPassedSlop = true;
-                                mInputMonitor.pilferPointers();
-                            }
-                        }
-                    } else {
-                        float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
-                                mLastPos.y - mDownPos.y);
-                        if (distance > mDragDistThreshold) {
-                            mIsStopGesture = true;
-                        }
-                    }
-                    break;
-                case MotionEvent.ACTION_UP:
-                    if (mLastPos.y >= mDownPos.y && mPassedSlop) {
-                        mGestureEventCallback.onStart();
-                    } else if (mIsStopGesture) {
-                        mGestureEventCallback.onStop();
-                    }
-                    clearState();
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                    clearState();
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    private void clearState() {
-        mPassedSlop = false;
-        mIsStopGesture = false;
-    }
-
-    private void disposeInputChannel() {
-        if (mInputEventReceiver != null) {
-            mInputEventReceiver.dispose();
-            mInputEventReceiver = null;
-        }
-
-        if (mInputMonitor != null) {
-            mInputMonitor.dispose();
-            mInputMonitor = null;
-        }
-    }
-
-    private boolean isWithinTouchRegion(float x, float y) {
-        return mGestureRegion.contains(Math.round(x), Math.round(y));
-    }
-
-    private int getNavBarSize(@NonNull DisplayLayout displayLayout) {
-        return isGestureAvailable() ? displayLayout.navBarFrameHeight() : 0 /* In landscape */;
-    }
-
-    private void updateIsEnabled() {
-        disposeInputChannel();
-
-        if (mIsEnabled && mIsThreeButtonModeEnabled && isGestureAvailable()) {
-            mInputMonitor = InputManager.getInstance().monitorGestureInput(
-                    "onehanded-gesture-offset", DEFAULT_DISPLAY);
-            try {
-                mMainExecutor.executeBlocking(() -> {
-                    mInputEventReceiver = new EventReceiver(
-                            mInputMonitor.getInputChannel(), Looper.myLooper());
-                });
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to create input event receiver", e);
-            }
-        }
-    }
-
-    private void onInputEvent(InputEvent ev) {
-        if (ev instanceof MotionEvent) {
-            onMotionEvent((MotionEvent) ev);
-        }
-    }
-
-    /**
-     * Handler for display rotation changes by {@link DisplayLayout}
-     *
-     * @param displayLayout The rotated displayLayout
-     */
-    public void onRotateDisplay(DisplayLayout displayLayout) {
-        mRotation = displayLayout.rotation();
-        mNavGestureHeight = getNavBarSize(displayLayout);
-        mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
-                displayLayout.height());
-        updateIsEnabled();
-    }
-
-    // TODO: Use BatchedInputEventReceiver
-    private class EventReceiver extends InputEventReceiver {
-        EventReceiver(InputChannel channel, Looper looper) {
-            super(channel, looper);
-        }
-
-        public void onInputEvent(InputEvent event) {
-            OneHandedGestureHandler.this.onInputEvent(event);
-            finishInputEvent(event, true);
-        }
-    }
-
-    private boolean isGestureAvailable() {
-        // Either OHM or swipe notification shade can activate in portrait mode only
-        return mRotation == Surface.ROTATION_0 || mRotation == Surface.ROTATION_180;
-    }
-
-    private boolean isValidStartAngle(float deltaX, float deltaY) {
-        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
-        return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
-    }
-
-    private boolean isValidExitAngle(float deltaX, float deltaY) {
-        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
-        return angle > ANGLE_MIN && angle < ANGLE_MAX;
-    }
-
-    private float squaredHypot(float x, float y) {
-        return x * x + y * y;
-    }
-
-    void dump(@NonNull PrintWriter pw) {
-        final String innerPrefix = "  ";
-        pw.println(TAG);
-        pw.print(innerPrefix + "mAllowGesture=");
-        pw.println(mAllowGesture);
-        pw.print(innerPrefix + "mIsEnabled=");
-        pw.println(mIsEnabled);
-        pw.print(innerPrefix + "mGestureRegion=");
-        pw.println(mGestureRegion);
-        pw.print(innerPrefix + "mNavGestureHeight=");
-        pw.println(mNavGestureHeight);
-        pw.print(innerPrefix + "mIsThreeButtonModeEnabled=");
-        pw.println(mIsThreeButtonModeEnabled);
-        pw.print(innerPrefix + "mRotation=");
-        pw.println(mRotation);
-    }
-
-    /**
-     * The touch(gesture) events to notify {@link OneHandedController} start or stop one handed
-     */
-    public interface OneHandedGestureEventCallback {
-        /**
-         * Handles the start gesture.
-         */
-        void onStart();
-
-        /**
-         * Handles the exit gesture.
-         */
-        void onStop();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 0f9b320..5b9f0c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -37,7 +37,6 @@
 /**
  * Manages all the touch handling for One Handed on the Phone, including user tap outside region
  * to exit, reset timer when user is in one-handed mode.
- * Refer {@link OneHandedGestureHandler} to see start and stop one handed gesture
  */
 public class OneHandedTouchHandler implements OneHandedTransitionCallback {
     private static final String TAG = "OneHandedTouchHandler";
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 99342f0..25d90b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -79,8 +78,6 @@
     @Mock
     OneHandedTutorialHandler mMockTutorialHandler;
     @Mock
-    OneHandedGestureHandler mMockGestureHandler;
-    @Mock
     OneHandedSettingsUtil mMockSettingsUitl;
     @Mock
     OneHandedUiEventLogger mMockUiEventLogger;
@@ -131,7 +128,6 @@
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
-                mMockGestureHandler,
                 mMockSettingsUitl,
                 mOneHandedAccessibilityUtil,
                 mSpiedTimeoutHandler,
@@ -179,7 +175,6 @@
     @Test
     public void testRegisterTransitionCallbackAfterInit() {
         verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockTouchHandler);
-        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockGestureHandler);
         verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockTutorialHandler);
     }
 
@@ -205,7 +200,6 @@
         mSpiedOneHandedController.setOneHandedEnabled(true);
 
         verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
-        verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
     }
 
     @Test
@@ -213,7 +207,6 @@
         mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled);
 
         verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
-        verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
     }
 
     @Test
@@ -340,30 +333,6 @@
     }
 
     @Test
-    public void testDisabled3ButtonGestureWhenKeyguardOn() {
-        final boolean isOneHandedEnabled = true;
-        final boolean isLockWhenKeyguardOn = true;
-        final boolean isEnabledWhenKeyguardOn = false;
-        mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
-        mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
-
-        verify(mMockGestureHandler).onGestureEnabled(isEnabledWhenKeyguardOn);
-    }
-
-    @Test
-    public void testEnabled3ButtonGestureWhenKeyguardGoingAway() {
-        final boolean isOneHandedEnabled = true;
-        final boolean isLockWhenKeyguardOn = false;
-        final boolean isEnabledWhenKeyguardOn = false;
-        mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
-        reset(mMockGestureHandler);
-
-        mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
-
-        verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled);
-    }
-
-    @Test
     public void testStateActive_shortcutRequestActivate_skipActions() {
         when(mSpiedTransitionState.getState()).thenReturn(STATE_ACTIVE);
         when(mSpiedTransitionState.isTransitioning()).thenReturn(false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
deleted file mode 100644
index 5d82a70..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.onehanded;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.view.Surface;
-import android.view.ViewConfiguration;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class OneHandedGestureHandlerTest extends OneHandedTestCase {
-    OneHandedGestureHandler mGestureHandler;
-    DisplayLayout mDisplayLayout;
-    @Mock
-    DisplayLayout mMockDisplayLayout;
-    @Mock
-    ShellExecutor mMockShellMainExecutor;
-
-    @Before
-    public void setUp() {
-        final int mockNavBarHeight = 100;
-        MockitoAnnotations.initMocks(this);
-        mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
-        mGestureHandler = new OneHandedGestureHandler(mContext, mDisplayLayout,
-                ViewConfiguration.get(mTestContext), mMockShellMainExecutor);
-        when(mMockDisplayLayout.navBarFrameHeight()).thenReturn(mockNavBarHeight);
-    }
-
-    @Test
-    public void testSetGestureEventListener() {
-        OneHandedGestureHandler.OneHandedGestureEventCallback callback = 
-            new OneHandedGestureHandler.OneHandedGestureEventCallback() {
-                @Override
-                public void onStart() {}
-
-                @Override
-                public void onStop() {}
-            };
-
-        mGestureHandler.setGestureEventListener(callback);
-        assertThat(mGestureHandler.mGestureEventCallback).isEqualTo(callback);
-    }
-
-    @Test
-    public void testOneHandedDisabled_shouldDisposeInputChannel() {
-        mGestureHandler.onGestureEnabled(false);
-
-        assertThat(mGestureHandler.mInputMonitor).isNull();
-        assertThat(mGestureHandler.mInputEventReceiver).isNull();
-    }
-
-    @Test
-    public void testChangeNavBarToNon3Button_shouldDisposeInputChannel() {
-        mGestureHandler.onGestureEnabled(true);
-        mGestureHandler.onThreeButtonModeEnabled(false);
-
-        assertThat(mGestureHandler.mInputMonitor).isNull();
-        assertThat(mGestureHandler.mInputEventReceiver).isNull();
-    }
-
-    @Test
-    public void testOnlyHandleGestureInPortraitMode() {
-        mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
-        mGestureHandler.onGestureEnabled(true);
-        mGestureHandler.onRotateDisplay(mDisplayLayout);
-
-        assertThat(mGestureHandler.mInputMonitor).isNull();
-        assertThat(mGestureHandler.mInputEventReceiver).isNull();
-    }
-
-    @Test
-    public void testRotation90ShouldNotRegisterEventReceiver() throws InterruptedException {
-        mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
-        mGestureHandler.onGestureEnabled(true);
-        mGestureHandler.onRotateDisplay(mDisplayLayout);
-
-        verify(mMockShellMainExecutor, never()).executeBlocking(any());
-    }
-
-    @Test
-    public void testRotation180ShouldNotRegisterEventReceiver() throws InterruptedException {
-        mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
-        mGestureHandler.onGestureEnabled(true);
-        mGestureHandler.onRotateDisplay(mDisplayLayout);
-
-        verify(mMockShellMainExecutor, never()).executeBlocking(any());
-    }
-
-    @Test
-    public void testRotation270ShouldNotRegisterEventReceiver() throws InterruptedException {
-        mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
-        mGestureHandler.onGestureEnabled(true);
-        mGestureHandler.onRotateDisplay(mDisplayLayout);
-
-        verify(mMockShellMainExecutor, never()).executeBlocking(any());
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index 89aae65..e61f061 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -74,8 +74,6 @@
     @Mock
     OneHandedTutorialHandler mMockTutorialHandler;
     @Mock
-    OneHandedGestureHandler mMockGestureHandler;
-    @Mock
     OneHandedSettingsUtil mMockSettingsUitl;
     @Mock
     OneHandedUiEventLogger mMockUiEventLogger;
@@ -126,7 +124,6 @@
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
-                mMockGestureHandler,
                 mMockSettingsUitl,
                 mOneHandedAccessibilityUtil,
                 mSpiedTimeoutHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index b82a8ca..5f2bfad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -44,8 +44,6 @@
     OneHandedState mSpiedTransitionState;
 
     @Mock
-    OneHandedGestureHandler mMockGestureHandler;
-    @Mock
     OneHandedTouchHandler mMockTouchHandler;
     @Mock
     OneHandedTutorialHandler mMockTutorialHandler;
@@ -84,7 +82,6 @@
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
-                mMockGestureHandler,
                 mMockSettingsUtil,
                 mMockAccessibilityUtil,
                 mTimeoutHandler,
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 52673c9..bb239f7 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -296,7 +296,6 @@
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
     method @NonNull public android.net.NetworkCapabilities build();
-    method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
     method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -310,6 +309,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
     method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+    method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
   }
 
   public class NetworkProvider {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 2e4d8f8..4932952 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2416,6 +2416,11 @@
         return mTransportInfo.getApplicableRedactions();
     }
 
+    private NetworkCapabilities removeDefaultCapabilites() {
+        mNetworkCapabilities &= ~DEFAULT_CAPABILITIES;
+        return this;
+    }
+
     /**
      * Builder class for NetworkCapabilities.
      *
@@ -2452,6 +2457,16 @@
         }
 
         /**
+         * Creates a new Builder without the default capabilities.
+         */
+        @NonNull
+        public static Builder withoutDefaultCapabilities() {
+            final NetworkCapabilities nc = new NetworkCapabilities();
+            nc.removeDefaultCapabilites();
+            return new Builder(nc);
+        }
+
+        /**
          * Adds the given transport type.
          *
          * Multiple transports may be added. Note that when searching for a network to satisfy a
@@ -2512,17 +2527,6 @@
         }
 
         /**
-         * Completely clears the contents of this object, removing even the capabilities that are
-         * set by default when the object is constructed.
-         * @return this builder
-         */
-        @NonNull
-        public Builder clearAll() {
-            mCaps.clearAll();
-            return this;
-        }
-
-        /**
          * Sets the owner UID.
          *
          * The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/packages/Connectivity/service/src/com/android/server/ConnectivityService.java b/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
index ed2fe82..a6e1e9e 100644
--- a/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
+++ b/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
@@ -1395,7 +1395,7 @@
         // arguments like the handler or the DnsResolver.
         // TODO : remove this ; it is probably better handled with a sentinel request.
         mNoServiceNetwork = new NetworkAgentInfo(null,
-                new Network(NO_SERVICE_NET_ID),
+                new Network(INetd.UNREACHABLE_NET_ID),
                 new NetworkInfo(TYPE_NONE, 0, "", ""),
                 new LinkProperties(), new NetworkCapabilities(),
                 new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
@@ -6466,8 +6466,6 @@
     // Request used to optionally keep vehicle internal network always active
     private final NetworkRequest mDefaultVehicleRequest;
 
-    // TODO replace with INetd.UNREACHABLE_NET_ID when available.
-    private static final int NO_SERVICE_NET_ID = 52;
     // Sentinel NAI used to direct apps with default networks that should have no connectivity to a
     // network with no service. This NAI should never be matched against, nor should any public API
     // ever return the associated network. For this reason, this NAI is not in the list of available
diff --git a/packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java b/packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java
index e839837..d7eb9c8 100644
--- a/packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -108,7 +108,58 @@
         }
     }
 
-    @Nullable private <T extends Scoreable> T getBestNetworkByPolicy(
+    private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) {
+        return candidate.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD)
+                && candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI);
+    }
+
+    /**
+     * Apply the "yield to bad WiFi" policy.
+     *
+     * This function must run immediately after the validation policy.
+     *
+     * If any of the accepted networks has the "yield to bad WiFi" policy AND there are some
+     * bad WiFis in the rejected list, then move the networks with the policy to the rejected
+     * list. If this leaves no accepted network, then move the bad WiFis back to the accepted list.
+     *
+     * This function returns nothing, but will have updated accepted and rejected in-place.
+     *
+     * @param accepted networks accepted by the validation policy
+     * @param rejected networks rejected by the validation policy
+     */
+    private <T extends Scoreable> void applyYieldToBadWifiPolicy(@NonNull ArrayList<T> accepted,
+            @NonNull ArrayList<T> rejected) {
+        if (!CollectionUtils.any(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) {
+            // No network with the policy : do nothing.
+            return;
+        }
+        if (!CollectionUtils.any(rejected, n -> isBadWiFi(n))) {
+            // No bad WiFi : do nothing.
+            return;
+        }
+        if (CollectionUtils.all(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) {
+            // All validated networks yield to bad WiFis : keep bad WiFis alongside with the
+            // yielders. This is important because the yielders need to be compared to the bad
+            // wifis by the following policies (e.g. exiting).
+            final ArrayList<T> acceptedYielders = new ArrayList<>(accepted);
+            final ArrayList<T> rejectedWithBadWiFis = new ArrayList<>(rejected);
+            partitionInto(rejectedWithBadWiFis, n -> isBadWiFi(n), accepted, rejected);
+            accepted.addAll(acceptedYielders);
+            return;
+        }
+        // Only some of the validated networks yield to bad WiFi : keep only the ones who don't.
+        final ArrayList<T> acceptedWithYielders = new ArrayList<>(accepted);
+        partitionInto(acceptedWithYielders, n -> !n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
+                accepted, rejected);
+    }
+
+    /**
+     * Get the best network among a list of candidates according to policy.
+     * @param candidates the candidates
+     * @param currentSatisfier the current satisfier, or null if none
+     * @return the best network
+     */
+    @Nullable public <T extends Scoreable> T getBestNetworkByPolicy(
             @NonNull List<T> candidates,
             @Nullable final T currentSatisfier) {
         // Used as working areas.
@@ -148,24 +199,15 @@
         if (accepted.size() == 1) return accepted.get(0);
         if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
 
-        // Yield to bad wifi policy : if any wifi has ever been validated (even if it's now
-        // unvalidated), and unless it's been explicitly avoided when bad in UI, then keep only
-        // networks that don't yield to such a wifi network.
-        final boolean anyWiFiEverValidated = CollectionUtils.any(candidates,
-                nai -> nai.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD)
-                        && nai.getCapsNoCopy().hasTransport(TRANSPORT_WIFI));
-        if (anyWiFiEverValidated) {
-            partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
-                    accepted, rejected);
-            if (accepted.size() == 1) return accepted.get(0);
-            if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
-        }
-
         // If any network is validated (or should be accepted even if it's not validated), then
         // don't choose one that isn't.
         partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED)
                         || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
                 accepted, rejected);
+        // Yield to bad wifi policy : if any network has the "yield to bad WiFi" policy and
+        // there are bad WiFis connected, then accept the bad WiFis and reject the networks with
+        // the policy.
+        applyYieldToBadWifiPolicy(accepted, rejected);
         if (accepted.size() == 1) return accepted.get(0);
         if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
 
@@ -194,16 +236,26 @@
         // subscription with the same transport.
         partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY),
                 accepted, rejected);
-        for (final Scoreable defaultSubNai : accepted) {
-            // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports
-            // as a network that has it.
-            final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes();
-            candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY)
-                    && Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes()));
+        if (accepted.size() > 0) {
+            // Some networks are primary for their transport. For each transport, keep only the
+            // primary, but also keep all networks for which there isn't a primary (which are now
+            // in the |rejected| array).
+            // So for each primary network, remove from |rejected| all networks with the same
+            // transports as one of the primary networks. The remaining networks should be accepted.
+            for (final T defaultSubNai : accepted) {
+                final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes();
+                rejected.removeIf(
+                        nai -> Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes()));
+            }
+            // Now the |rejected| list contains networks with transports for which there isn't
+            // a primary network. Add them back to the candidates.
+            accepted.addAll(rejected);
+            candidates = new ArrayList<>(accepted);
         }
         if (1 == candidates.size()) return candidates.get(0);
-        // It's guaranteed candidates.size() > 0 because there is at least one with the
-        // TRANSPORT_PRIMARY policy and only those without it were removed.
+        // If there were no primary network, then candidates.size() > 0 because it didn't
+        // change from the previous result. If there were, it's guaranteed candidates.size() > 0
+        // because accepted.size() > 0 above.
 
         // If some of the networks have a better transport than others, keep only the ones with
         // the best transports.
diff --git a/packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 551b94c..4408958 100644
--- a/packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,74 +17,156 @@
 package com.android.server.connectivity
 
 import android.net.NetworkCapabilities
-import android.net.NetworkRequest
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.net.NetworkScore.POLICY_EXITING
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
+import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI
+import android.os.Build
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD
+import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
 import kotlin.test.assertEquals
-import kotlin.test.assertNull
 
-@RunWith(AndroidJUnit4::class)
+private fun score(vararg policies: Int) = FullScore(0,
+        policies.fold(0L) { acc, e -> acc or (1L shl e) }, KEEP_CONNECTED_NONE)
+private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportType(transport).build()
+
 @SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class NetworkRankerTest {
-    private val ranker = NetworkRanker()
+    private val mRanker = NetworkRanker()
 
-    private fun makeNai(satisfy: Boolean, legacyScore: Int) =
-            mock(NetworkAgentInfo::class.java).also {
-                doReturn(satisfy).`when`(it).satisfies(any())
-                val fs = FullScore(legacyScore, 0 /* policies */, KEEP_CONNECTED_NONE)
-                doReturn(fs).`when`(it).getScore()
-                val nc = NetworkCapabilities.Builder().build()
-                doReturn(nc).`when`(it).getCapsNoCopy()
-            }
-
-    @Test
-    fun testGetBestNetwork() {
-        val scores = listOf(20, 50, 90, 60, 23, 68)
-        val nais = scores.map { makeNai(true, it) }
-        val bestNetwork = nais[2] // The one with the top score
-        val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork))
+    private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
+            : NetworkRanker.Scoreable {
+        override fun getScore() = sc
+        override fun getCapsNoCopy(): NetworkCapabilities = nc
     }
 
     @Test
-    fun testIgnoreNonSatisfying() {
-        val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90),
-                makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
-        val bestNetwork = nais[1] // Top score that's satisfying
-        val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1]))
+    fun testYieldToBadWiFiOneCell() {
+        // Only cell, it wins
+        val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR))
+        val scores = listOf(winner)
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
     }
 
     @Test
-    fun testNoMatch() {
-        val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
-        val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, nais, null))
+    fun testYieldToBadWiFiOneCellOneBadWiFi() {
+        // Bad wifi wins against yielding validated cell
+        val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+                caps(TRANSPORT_WIFI))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
     }
 
     @Test
-    fun testEmpty() {
-        val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, emptyList(), null))
+    fun testYieldToBadWiFiOneCellTwoBadWiFi() {
+        // Bad wifi wins against yielding validated cell. Prefer the one that's primary.
+        val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+                        caps(TRANSPORT_WIFI)),
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
     }
 
-    // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
-    // network satisfying the request if multiple of them have the same score.
     @Test
-    fun testStable() {
-        val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
-                makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
-        val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0]))
+    fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() {
+        // Bad wifi ever validated wins against bad wifi that never was validated (or was
+        // avoided when bad).
+        val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+                caps(TRANSPORT_WIFI))
+        val scores = listOf(
+                winner,
+                TestScore(score(), caps(TRANSPORT_WIFI)),
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    }
 
-        val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
-                makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
-        assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1]))
+    @Test
+    fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() {
+        // Good wifi wins
+        val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                        POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    }
+
+    @Test
+    fun testYieldToBadWiFiTwoCellsOneBadWiFi() {
+        // Cell that doesn't yield wins over cell that yields and bad wifi
+        val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                        POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    }
+
+    @Test
+    fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() {
+        // Good wifi wins over cell that doesn't yield and cell that yields
+        val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                        POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+                TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)),
+                TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                        caps(TRANSPORT_CELLULAR))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    }
+
+    @Test
+    fun testYieldToBadWiFiOneExitingGoodWiFi() {
+        // Yielding cell wins over good exiting wifi
+        val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_IS_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    }
+
+    @Test
+    fun testYieldToBadWiFiOneExitingBadWiFi() {
+        // Yielding cell wins over bad exiting wifi
+        val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR))
+        val scores = listOf(
+                winner,
+                TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+                        POLICY_EXITING), caps(TRANSPORT_WIFI))
+        )
+        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 779ba1c..8465889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
@@ -29,6 +32,7 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.Gravity;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
@@ -49,6 +53,8 @@
     private int mRightInset = 0;
     private int mTopInset = 0;
 
+    private float mTouchDownY = 0;
+
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -67,6 +73,28 @@
         return windowInsets;
     }
 
+    /**
+     * This is specifically for pulling down the status bar as a consistent motion in the visual
+     * immersive mode. In the visual immersive mode, after the system detects a system gesture
+     * motion from the top, we show permanent bars, and then forward the touch events from the
+     * focused window to the status bar window. However, since the first relayed event is out of
+     * bound of the status bar view, in order for the touch event to be correctly dispatched down,
+     * we jot down the position Y of the initial touch down event, offset it to 0 in the y-axis,
+     * and calculate the movement based on first touch down position.
+     */
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == ACTION_DOWN && ev.getRawY() > getHeight()) {
+            mTouchDownY = ev.getRawY();
+            ev.setLocation(ev.getRawX(), mTopInset);
+        } else if (ev.getAction() == ACTION_MOVE && mTouchDownY != 0) {
+            ev.setLocation(ev.getRawX(), mTopInset + ev.getRawY() - mTouchDownY);
+        } else if (ev.getAction() == ACTION_UP) {
+            mTouchDownY = 0;
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
     private void applyMargins() {
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 74077a2..92ef850 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -17,7 +17,6 @@
 package com.android.systemui.wmshell;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
@@ -35,7 +34,6 @@
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.view.KeyEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -59,7 +57,6 @@
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
@@ -230,10 +227,6 @@
 
     @VisibleForTesting
     void initOneHanded(OneHanded oneHanded) {
-        int currentMode = mNavigationModeController.addListener(mode ->
-                oneHanded.setThreeButtonModeEnabled(mode == NAV_BAR_MODE_3BUTTON));
-        oneHanded.setThreeButtonModeEnabled(currentMode == NAV_BAR_MODE_3BUTTON);
-
         oneHanded.registerTransitionCallback(new OneHandedTransitionCallback() {
             @Override
             public void onStartTransition(boolean isEntering) {
@@ -260,32 +253,6 @@
             }
         });
 
-        oneHanded.registerGestureCallback(new OneHandedGestureEventCallback() {
-            @Override
-            public void onStart() {
-                mSysUiMainExecutor.execute(() -> {
-                    if (oneHanded.isOneHandedEnabled()) {
-                        oneHanded.startOneHanded();
-                    } else if (oneHanded.isSwipeToNotificationEnabled()) {
-                        mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
-                    }
-                });
-            }
-
-            @Override
-            public void onStop() {
-                mSysUiMainExecutor.execute(() -> {
-                    if (oneHanded.isOneHandedEnabled()) {
-                        // Log metrics for 3-button navigation mode.
-                        oneHanded.stopOneHanded(
-                                OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
-                    } else if (oneHanded.isSwipeToNotificationEnabled()) {
-                        mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP);
-                    }
-                });
-            }
-        });
-
         mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
             public void onKeyguardBouncerChanged(boolean bouncer) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 79641db..1dd0b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -37,7 +37,6 @@
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
 
@@ -106,11 +105,6 @@
         verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
         verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
         verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
-        verify(mNavigationModeController).addListener(
-                any(NavigationModeController.ModeChangedListener.class));
-
-        verify(mOneHanded).registerGestureCallback(any(
-                OneHandedGestureHandler.OneHandedGestureEventCallback.class));
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index dcd0eb8..37ee76b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -262,18 +262,6 @@
         return super.handleUserControlPressed(message);
     }
 
-    @Override
-    protected void wakeUpIfActiveSource() {
-        if (!isActiveSource()) {
-            return;
-        }
-        // Wake up the device if the power is in standby mode, or its screen is off -
-        // which can happen if the device is holding a partial lock.
-        if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
-            mService.wakeUp();
-        }
-    }
-
     @ServiceThreadOnly
     @Constants.HandleMessageResult
     protected int handleSetMenuLanguage(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 702f854..1c726e0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -380,7 +380,7 @@
         if (!isActiveSource()) {
             return;
         }
-        // Wake up the device
+        // Wake up the device. This will also exit dream mode.
         mService.wakeUp();
         return;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 77de187..2ed160a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -442,18 +442,85 @@
 
     public HdmiControlService(Context context) {
         super(context);
-        List<Integer> deviceTypes = HdmiProperties.device_type();
-        if (deviceTypes.contains(null)) {
-            Slog.w(TAG, "Error parsing ro.hdmi.device.type: " + SystemProperties.get(
-                    "ro.hdmi.device_type"));
-            deviceTypes = deviceTypes.stream().filter(Objects::nonNull).collect(
-                    Collectors.toList());
-        }
-        mLocalDevices = deviceTypes;
+        mLocalDevices = readDeviceTypes();
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
     }
 
+    @VisibleForTesting
+    protected List<HdmiProperties.cec_device_types_values> getCecDeviceTypes() {
+        return HdmiProperties.cec_device_types();
+    }
+
+    @VisibleForTesting
+    protected List<Integer> getDeviceTypes() {
+        return HdmiProperties.device_type();
+    }
+
+    /**
+     * Extracts a list of integer device types from the sysprop ro.hdmi.cec_device_types.
+     * If ro.hdmi.cec_device_types is not set, reads from ro.hdmi.device.type instead.
+     * @return the list of integer device types
+     */
+    @VisibleForTesting
+    protected List<Integer> readDeviceTypes() {
+        List<HdmiProperties.cec_device_types_values> cecDeviceTypes = getCecDeviceTypes();
+        if (!cecDeviceTypes.isEmpty()) {
+            if (cecDeviceTypes.contains(null)) {
+                Slog.w(TAG, "Error parsing ro.hdmi.cec_device_types: " + SystemProperties.get(
+                        "ro.hdmi.cec_device_types"));
+            }
+            return cecDeviceTypes.stream()
+                    .map(HdmiControlService::enumToIntDeviceType)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        } else {
+            // If ro.hdmi.cec_device_types isn't set, fall back to reading ro.hdmi.device_type
+            List<Integer> deviceTypes = getDeviceTypes();
+            if (deviceTypes.contains(null)) {
+                Slog.w(TAG, "Error parsing ro.hdmi.device_type: " + SystemProperties.get(
+                        "ro.hdmi.device_type"));
+            }
+            return deviceTypes.stream()
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        }
+    }
+
+    /**
+     * Converts an enum representing a value in ro.hdmi.cec_device_types to an integer device type.
+     * Returns null if the input is null or an unrecognized device type.
+     */
+    @Nullable
+    private static Integer enumToIntDeviceType(
+            @Nullable HdmiProperties.cec_device_types_values cecDeviceType) {
+        if (cecDeviceType == null) {
+            return null;
+        }
+        switch (cecDeviceType) {
+            case TV:
+                return HdmiDeviceInfo.DEVICE_TV;
+            case RECORDING_DEVICE:
+                return HdmiDeviceInfo.DEVICE_RECORDER;
+            case RESERVED:
+                return HdmiDeviceInfo.DEVICE_RESERVED;
+            case TUNER:
+                return HdmiDeviceInfo.DEVICE_TUNER;
+            case PLAYBACK_DEVICE:
+                return HdmiDeviceInfo.DEVICE_PLAYBACK;
+            case AUDIO_SYSTEM:
+                return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+            case PURE_CEC_SWITCH:
+                return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
+            case VIDEO_PROCESSOR:
+                return HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR;
+            default:
+                Slog.w(TAG, "Unrecognized device type in ro.hdmi.cec_device_types: "
+                        + cecDeviceType.getPropValue());
+                return null;
+        }
+    }
+
     protected static List<Integer> getIntList(String string) {
         ArrayList<Integer> list = new ArrayList<>();
         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
@@ -3030,6 +3097,7 @@
     @ServiceThreadOnly
     @VisibleForTesting
     protected void onStandby(final int standbyAction) {
+        mWakeUpMessageReceived = false;
         assertRunOnServiceThread();
         mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
                 false);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 37e15c7..9d33a69 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2424,6 +2424,15 @@
         } else {
             // Restore visibilities and positions of system bars.
             controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
+            // To further allow the pull-down-from-the-top gesture to pull down the notification
+            // shade as a consistent motion, we reroute the touch events here from the currently
+            // touched window to the status bar after making it visible.
+            if (swipeTarget == mStatusBar) {
+                final boolean transferred = mStatusBar.transferTouch();
+                if (!transferred) {
+                    Slog.i(TAG, "Could not transfer touch to the status bar");
+                }
+            }
         }
         mImmersiveModeConfirmation.confirmCurrentPrompt();
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 5fbf8de..524ad62 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -137,6 +137,11 @@
                     }
 
                     @Override
+                    boolean isPowerStandbyOrTransient() {
+                        return false;
+                    }
+
+                    @Override
                     protected PowerManager getPowerManager() {
                         return new PowerManager(context, mIPowerManagerMock,
                                 mIThermalServiceMock, new Handler(mMyLooper));
@@ -1335,6 +1340,22 @@
     }
 
     @Test
+    public void handleSetStreamPath_Dreaming() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        mWokenUp = false;
+
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+                        mPlaybackPhysicalAddress);
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+        assertThat(mWokenUp).isTrue();
+    }
+
+    @Test
     public void handleSetStreamPath_otherDevice_None() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index bcf30a2..0cf212c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK;
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
 
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
@@ -47,6 +48,7 @@
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.sysprop.HdmiProperties;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -828,4 +830,71 @@
         assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
                 .isEqualTo(Constants.ABORT_REFUSED);
     }
+
+    @Test
+    public void readDeviceTypes_readsIntegerDeviceTypes() {
+        doReturn(Arrays.asList(new Integer[]{DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM}))
+                .when(mHdmiControlServiceSpy).getDeviceTypes();
+        doReturn(Arrays.asList(new HdmiProperties.cec_device_types_values[]{}))
+                .when(mHdmiControlServiceSpy).getCecDeviceTypes();
+
+        assertThat(mHdmiControlServiceSpy.readDeviceTypes())
+                .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
+    }
+
+    @Test
+    public void readDeviceTypes_readsEnumDeviceTypes() {
+        doReturn(Arrays.asList(new Integer[]{})).when(mHdmiControlServiceSpy).getDeviceTypes();
+        doReturn(Arrays.asList(
+                new HdmiProperties.cec_device_types_values[]{
+                        HdmiProperties.cec_device_types_values.PLAYBACK_DEVICE,
+                        HdmiProperties.cec_device_types_values.AUDIO_SYSTEM
+                }))
+                .when(mHdmiControlServiceSpy).getCecDeviceTypes();
+
+        assertThat(mHdmiControlServiceSpy.readDeviceTypes())
+                .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
+    }
+
+    @Test
+    public void readDeviceTypes_readsEnumOverIntegerDeviceTypes() {
+        doReturn(Arrays.asList(new Integer[]{DEVICE_TV}))
+                .when(mHdmiControlServiceSpy).getDeviceTypes();
+        doReturn(Arrays.asList(
+                new HdmiProperties.cec_device_types_values[]{
+                        HdmiProperties.cec_device_types_values.PLAYBACK_DEVICE,
+                        HdmiProperties.cec_device_types_values.AUDIO_SYSTEM
+                }))
+                .when(mHdmiControlServiceSpy).getCecDeviceTypes();
+
+        assertThat(mHdmiControlServiceSpy.readDeviceTypes())
+                .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
+    }
+
+    @Test
+    public void readDeviceTypes_doesNotReadNullEnumDeviceType() {
+        doReturn(Arrays.asList(new Integer[]{})).when(mHdmiControlServiceSpy).getDeviceTypes();
+        doReturn(Arrays.asList(
+                new HdmiProperties.cec_device_types_values[]{
+                        HdmiProperties.cec_device_types_values.PLAYBACK_DEVICE,
+                        HdmiProperties.cec_device_types_values.AUDIO_SYSTEM,
+                        null
+                }))
+                .when(mHdmiControlServiceSpy).getCecDeviceTypes();
+
+        assertThat(mHdmiControlServiceSpy.readDeviceTypes())
+                .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
+    }
+
+    @Test
+    public void readDeviceTypes_doesNotReadNullIntegerDeviceType() {
+        doReturn(Arrays.asList(new Integer[]{DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM, null}))
+                .when(mHdmiControlServiceSpy).getDeviceTypes();
+        doReturn(Arrays.asList(new HdmiProperties.cec_device_types_values[]{}))
+                .when(mHdmiControlServiceSpy).getCecDeviceTypes();
+
+        assertThat(mHdmiControlServiceSpy.readDeviceTypes())
+                .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
+    }
+
 }