Add mModeSwitchingType to DisplayModeDirector

Add mModeSwitchingType to DisplayModeDirector which describes
the allow mode switching type for SurfaceFlinger. This value
is piped to SurfaceFlinger via allowGroupSwitching in
DesiredDisplayModeSpecs.

The value for mModeSwitchingType defaults to
SWITCHING_TYPE_WITHIN_GROUPS, which is the current beheviour in
SurfaceFlinger and it'll be later updated from user settings.

Bug: 161776333
Test: not yet
Change-Id: I851eaee7f86083d97204cf3553e728350c942dfe
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bbdd7d3..fdccfd8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1897,16 +1897,23 @@
         public float appRequestRefreshRateMin;
         public float appRequestRefreshRateMax;
 
+        /**
+         * If true this will allow switching between modes in different display configuration
+         * groups. This way the user may see visual interruptions when the display mode changes.
+         */
+        public boolean allowGroupSwitching;
+
         public DesiredDisplayConfigSpecs() {}
 
         public DesiredDisplayConfigSpecs(DesiredDisplayConfigSpecs other) {
             copyFrom(other);
         }
 
-        public DesiredDisplayConfigSpecs(int defaultConfig, float primaryRefreshRateMin,
-                float primaryRefreshRateMax, float appRequestRefreshRateMin,
-                float appRequestRefreshRateMax) {
+        public DesiredDisplayConfigSpecs(int defaultConfig, boolean allowGroupSwitching,
+                float primaryRefreshRateMin, float primaryRefreshRateMax,
+                float appRequestRefreshRateMin, float appRequestRefreshRateMax) {
             this.defaultConfig = defaultConfig;
+            this.allowGroupSwitching = allowGroupSwitching;
             this.primaryRefreshRateMin = primaryRefreshRateMin;
             this.primaryRefreshRateMax = primaryRefreshRateMax;
             this.appRequestRefreshRateMin = appRequestRefreshRateMin;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a61903d..59488c1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -196,6 +196,7 @@
     jclass clazz;
     jmethodID ctor;
     jfieldID defaultConfig;
+    jfieldID allowGroupSwitching;
     jfieldID primaryRefreshRateMin;
     jfieldID primaryRefreshRateMax;
     jfieldID appRequestRefreshRateMin;
@@ -998,6 +999,9 @@
 
     jint defaultConfig = env->GetIntField(desiredDisplayConfigSpecs,
                                           gDesiredDisplayConfigSpecsClassInfo.defaultConfig);
+    jboolean allowGroupSwitching =
+            env->GetBooleanField(desiredDisplayConfigSpecs,
+                                 gDesiredDisplayConfigSpecsClassInfo.allowGroupSwitching);
     jfloat primaryRefreshRateMin =
             env->GetFloatField(desiredDisplayConfigSpecs,
                                gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMin);
@@ -1012,6 +1016,7 @@
                                gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMax);
 
     size_t result = SurfaceComposerClient::setDesiredDisplayConfigSpecs(token, defaultConfig,
+                                                                        allowGroupSwitching,
                                                                         primaryRefreshRateMin,
                                                                         primaryRefreshRateMax,
                                                                         appRequestRefreshRateMin,
@@ -1024,11 +1029,13 @@
     if (token == nullptr) return nullptr;
 
     int32_t defaultConfig;
+    bool allowGroupSwitching;
     float primaryRefreshRateMin;
     float primaryRefreshRateMax;
     float appRequestRefreshRateMin;
     float appRequestRefreshRateMax;
     if (SurfaceComposerClient::getDesiredDisplayConfigSpecs(token, &defaultConfig,
+                                                            &allowGroupSwitching,
                                                             &primaryRefreshRateMin,
                                                             &primaryRefreshRateMax,
                                                             &appRequestRefreshRateMin,
@@ -1039,8 +1046,8 @@
 
     return env->NewObject(gDesiredDisplayConfigSpecsClassInfo.clazz,
                           gDesiredDisplayConfigSpecsClassInfo.ctor, defaultConfig,
-                          primaryRefreshRateMin, primaryRefreshRateMax, appRequestRefreshRateMin,
-                          appRequestRefreshRateMax);
+                          allowGroupSwitching, primaryRefreshRateMin, primaryRefreshRateMax,
+                          appRequestRefreshRateMin, appRequestRefreshRateMax);
 }
 
 static jint nativeGetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenObj) {
@@ -1857,9 +1864,11 @@
     gDesiredDisplayConfigSpecsClassInfo.clazz =
             MakeGlobalRefOrDie(env, desiredDisplayConfigSpecsClazz);
     gDesiredDisplayConfigSpecsClassInfo.ctor =
-            GetMethodIDOrDie(env, gDesiredDisplayConfigSpecsClassInfo.clazz, "<init>", "(IFFFF)V");
+            GetMethodIDOrDie(env, gDesiredDisplayConfigSpecsClassInfo.clazz, "<init>", "(IZFFFF)V");
     gDesiredDisplayConfigSpecsClassInfo.defaultConfig =
             GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "defaultConfig", "I");
+    gDesiredDisplayConfigSpecsClassInfo.allowGroupSwitching =
+            GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "allowGroupSwitching", "Z");
     gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMin =
             GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "primaryRefreshRateMin", "F");
     gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMax =
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 4f5a0fa..37bf3bd 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -50,11 +51,14 @@
 import com.android.server.display.utils.AmbientFilterFactory;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
+
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
  * picked by the system based on system-wide and display-specific configuration.
@@ -99,6 +103,29 @@
 
     private boolean mAlwaysRespectAppRequest;
 
+    @IntDef(prefix = {"SWITCHING_TYPE_"}, value = {
+            SWITCHING_TYPE_NONE,
+            SWITCHING_TYPE_WITHIN_GROUPS,
+            SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SwitchingType {}
+
+    // No mode switching will happen.
+    public static final int SWITCHING_TYPE_NONE = 0;
+    // Allow only refresh rate switching between modes in the same configuration group. This way
+    // only switches without visual interruptions for the user will be allowed.
+    public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1;
+    // Allow refresh rate switching between all refresh rates even if the switch with have visual
+    // interruptions for the user.
+    public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2;
+
+    /**
+     * The allowed refresh rate switching type. This is used by SurfaceFlinger.
+     */
+    @SwitchingType
+    private int mModeSwitchingType = SWITCHING_TYPE_WITHIN_GROUPS;
+
     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
@@ -291,14 +318,37 @@
                                 appRequestSummary.maxRefreshRate));
             }
 
+            // If the application requests a given mode with preferredModeId function, it will be
+            // stored as baseModeId.
             int baseModeId = defaultMode.getModeId();
             if (availableModes.length > 0) {
                 baseModeId = availableModes[0];
             }
-            // filterModes function is going to filter the modes based on the voting system. If
-            // the application requests a given mode with preferredModeId function, it will be
-            // stored as baseModeId.
+            if (mModeSwitchingType == SWITCHING_TYPE_NONE) {
+                Display.Mode baseMode = null;
+                for (Display.Mode mode : modes) {
+                    if (mode.getModeId() == baseModeId) {
+                        baseMode = mode;
+                        break;
+                    }
+                }
+                if (baseMode == null) {
+                    // This should never happen.
+                    throw new IllegalStateException(
+                            "The base mode with id " + baseModeId
+                                    + " is not in the list of supported modes.");
+                }
+                float fps = baseMode.getRefreshRate();
+                return new DesiredDisplayModeSpecs(baseModeId,
+                        /*allowGroupSwitching */ false,
+                        new RefreshRateRange(fps, fps),
+                        new RefreshRateRange(fps, fps));
+            }
+
+            boolean allowGroupSwitching =
+                    mModeSwitchingType == SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
             return new DesiredDisplayModeSpecs(baseModeId,
+                    allowGroupSwitching,
                     new RefreshRateRange(
                             primarySummary.minRefreshRate, primarySummary.maxRefreshRate),
                     new RefreshRateRange(
@@ -386,6 +436,26 @@
     }
 
     /**
+     * Sets the display mode switching type.
+     * @param type
+     */
+    public void setModeSwitchingType(@SwitchingType int type) {
+        synchronized (mLock) {
+            mModeSwitchingType = type;
+        }
+    }
+
+    /**
+     * Returns the display mode switching type.
+     */
+    @SwitchingType
+    public int getModeSwitchingType() {
+        synchronized (mLock) {
+            return mModeSwitchingType;
+        }
+    }
+
+    /**
      * Print the object's state and debug information into the given stream.
      *
      * @param pw The stream to dump information to.
@@ -417,6 +487,7 @@
                     pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
                 }
             }
+            pw.println("  mModeSwitchingType: " + switchingTypeToString(mModeSwitchingType));
             pw.println("  mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest);
             mSettingsObserver.dumpLocked(pw);
             mAppRequestObserver.dumpLocked(pw);
@@ -473,7 +544,6 @@
     }
 
     private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
-        int index = mVotesByDisplay.indexOfKey(displayId);
         if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
             return mVotesByDisplay.get(displayId);
         } else {
@@ -483,6 +553,19 @@
         }
     }
 
+    private static String switchingTypeToString(@SwitchingType int type) {
+        switch (type) {
+            case SWITCHING_TYPE_NONE:
+                return "SWITCHING_TYPE_NONE";
+            case SWITCHING_TYPE_WITHIN_GROUPS:
+                return "SWITCHING_TYPE_WITHIN_GROUPS";
+            case SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS:
+                return "SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS";
+            default:
+                return "Unknown SwitchingType " + type;
+        }
+    }
+
     @VisibleForTesting
     void injectSupportedModesByDisplay(SparseArray<Display.Mode[]> supportedModesByDisplay) {
         mSupportedModesByDisplay = supportedModesByDisplay;
@@ -632,18 +715,26 @@
      * SurfaceControl.DesiredDisplayConfigSpecs uses, and the mode ID used here.
      */
     public static final class DesiredDisplayModeSpecs {
+
         /**
          * Base mode ID. This is what system defaults to for all other settings, or
          * if the refresh rate range is not available.
          */
         public int baseModeId;
+
+        /**
+         * If true this will allow switching between modes in different display configuration
+         * groups. This way the user may see visual interruptions when the display mode changes.
+         */
+        public boolean allowGroupSwitching;
+
         /**
          * The primary refresh rate range.
          */
         public final RefreshRateRange primaryRefreshRateRange;
         /**
          * The app request refresh rate range. Lower priority considerations won't be included in
-         * this range, allowing surface flinger to consider additional refresh rates for apps that
+         * this range, allowing SurfaceFlinger to consider additional refresh rates for apps that
          * call setFrameRate(). This range will be greater than or equal to the primary refresh rate
          * range, never smaller.
          */
@@ -655,9 +746,11 @@
         }
 
         public DesiredDisplayModeSpecs(int baseModeId,
+                boolean allowGroupSwitching,
                 @NonNull RefreshRateRange primaryRefreshRateRange,
                 @NonNull RefreshRateRange appRequestRefreshRateRange) {
             this.baseModeId = baseModeId;
+            this.allowGroupSwitching = allowGroupSwitching;
             this.primaryRefreshRateRange = primaryRefreshRateRange;
             this.appRequestRefreshRateRange = appRequestRefreshRateRange;
         }
@@ -667,10 +760,12 @@
          */
         @Override
         public String toString() {
-            return String.format("baseModeId=%d primaryRefreshRateRange=[%.0f %.0f]"
+            return String.format("baseModeId=%d allowGroupSwitching=%b"
+                            + " primaryRefreshRateRange=[%.0f %.0f]"
                             + " appRequestRefreshRateRange=[%.0f %.0f]",
-                    baseModeId, primaryRefreshRateRange.min, primaryRefreshRateRange.max,
-                    appRequestRefreshRateRange.min, appRequestRefreshRateRange.max);
+                    baseModeId, allowGroupSwitching, primaryRefreshRateRange.min,
+                    primaryRefreshRateRange.max, appRequestRefreshRateRange.min,
+                    appRequestRefreshRateRange.max);
         }
         /**
          * Checks whether the two objects have the same values.
@@ -690,6 +785,9 @@
             if (baseModeId != desiredDisplayModeSpecs.baseModeId) {
                 return false;
             }
+            if (allowGroupSwitching != desiredDisplayModeSpecs.allowGroupSwitching) {
+                return false;
+            }
             if (!primaryRefreshRateRange.equals(desiredDisplayModeSpecs.primaryRefreshRateRange)) {
                 return false;
             }
@@ -702,7 +800,8 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(baseModeId, primaryRefreshRateRange, appRequestRefreshRateRange);
+            return Objects.hash(baseModeId, allowGroupSwitching, primaryRefreshRateRange,
+                    appRequestRefreshRateRange);
         }
 
         /**
@@ -710,6 +809,7 @@
          */
         public void copyFrom(DesiredDisplayModeSpecs other) {
             baseModeId = other.baseModeId;
+            allowGroupSwitching = other.allowGroupSwitching;
             primaryRefreshRateRange.min = other.primaryRefreshRateRange.min;
             primaryRefreshRateRange.max = other.primaryRefreshRateRange.max;
             appRequestRefreshRateRange.min = other.appRequestRefreshRateRange.min;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 6597aa5..31e9f23 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -811,6 +811,7 @@
                         LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
                         getDisplayTokenLocked(),
                         new SurfaceControl.DesiredDisplayConfigSpecs(baseConfigId,
+                                mDisplayModeSpecs.allowGroupSwitching,
                                 mDisplayModeSpecs.primaryRefreshRateRange.min,
                                 mDisplayModeSpecs.primaryRefreshRateRange.max,
                                 mDisplayModeSpecs.appRequestRefreshRateRange.min,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 0ab1501..aa18e63 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -389,7 +389,12 @@
         public Display.HdrCapabilities hdrCapabilities = new Display.HdrCapabilities(new int[0],
                 1000, 1000, 0);
         public SurfaceControl.DesiredDisplayConfigSpecs desiredDisplayConfigSpecs =
-                new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f, 60.f, 60.f);
+                new SurfaceControl.DesiredDisplayConfigSpecs(/* defaultConfig */ 0,
+                    /* allowGroupSwitching */ false,
+                    /* primaryRefreshRateMin */ 60.f,
+                    /* primaryRefreshRateMax */ 60.f,
+                    /* appRefreshRateMin */ 60.f,
+                    /* appRefreshRateMax */60.f);
 
         private FakeDisplay(int port) {
             this.address = createDisplayAddress(port);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index b8dbd62..325ba11 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 
@@ -33,8 +35,6 @@
 import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
 import com.android.server.display.DisplayModeDirector.Vote;
 
-import com.google.common.truth.Truth;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,9 +90,9 @@
         // With no votes present, DisplayModeDirector should allow any refresh rate.
         DesiredDisplayModeSpecs modeSpecs =
                 createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
-        Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
-        Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(modeSpecs.baseModeId).isEqualTo(60);
+        assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
+        assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
 
         int numPriorities =
                 DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1;
@@ -112,10 +112,10 @@
                 votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i));
                 director.injectVotesByDisplay(votesByDisplay);
                 modeSpecs = director.getDesiredDisplayModeSpecs(displayId);
-                Truth.assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i);
-                Truth.assertThat(modeSpecs.primaryRefreshRateRange.min)
+                assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i);
+                assertThat(modeSpecs.primaryRefreshRateRange.min)
                         .isEqualTo((float) (minFps + i));
-                Truth.assertThat(modeSpecs.primaryRefreshRateRange.max)
+                assertThat(modeSpecs.primaryRefreshRateRange.max)
                         .isEqualTo((float) (maxFps - i));
             }
         }
@@ -132,9 +132,9 @@
             votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80));
             director.injectVotesByDisplay(votesByDisplay);
             modeSpecs = director.getDesiredDisplayModeSpecs(displayId);
-            Truth.assertThat(modeSpecs.baseModeId).isEqualTo(70);
-            Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f);
-            Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f);
+            assertThat(modeSpecs.baseModeId).isEqualTo(70);
+            assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f);
+            assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f);
         }
     }
 
@@ -153,9 +153,9 @@
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
 
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
     }
 
     @Test
@@ -172,32 +172,32 @@
         votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
         votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
         votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60));
         votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
     }
 
     @Test
@@ -219,29 +219,29 @@
         votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
 
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
                 Vote.forRefreshRates(90, Float.POSITIVE_INFINITY));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
 
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min)
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.min)
                 .isWithin(FLOAT_TOLERANCE)
                 .of(75);
-        Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max)
+        assertThat(desiredSpecs.appRequestRefreshRateRange.max)
                 .isWithin(FLOAT_TOLERANCE)
                 .of(75);
     }
@@ -251,10 +251,10 @@
             float appRequestMin, float appRequestMax) {
         DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
                 minFps, peakFps, defaultFps);
-        Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
-        Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
-        Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
-        Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
+        assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
+        assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
+        assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
+        assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
     }
 
     @Test
@@ -318,26 +318,84 @@
 
 
         director.injectVotesByDisplay(votesByDisplay);
-        Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
 
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
 
         director.setShouldAlwaysRespectAppRequestedMode(true);
-        Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue();
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue();
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
-        Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
 
         director.setShouldAlwaysRespectAppRequestedMode(false);
-        Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
 
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
-        Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeNone() {
+        final int displayId = 0;
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(displayId, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60));
+
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayModeDirector.SWITCHING_TYPE_NONE);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+
+        director.setModeSwitchingType(DisplayModeDirector.SWITCHING_TYPE_NONE);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayModeDirector.SWITCHING_TYPE_NONE);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequestRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeWithinGroups() {
+        final int displayId = 0;
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+
+        director.setModeSwitchingType(DisplayModeDirector.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayModeDirector.SWITCHING_TYPE_WITHIN_GROUPS);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        assertThat(desiredSpecs.allowGroupSwitching).isFalse();
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeWithinAndAcrossGroups() {
+        final int displayId = 0;
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+
+        director.setModeSwitchingType(DisplayModeDirector.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayModeDirector.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        assertThat(desiredSpecs.allowGroupSwitching).isTrue();
     }
 }