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);
+ }
+
}