Merge "Allow settings a freeform string restriction mode" into qt-qpr1-dev
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
index 1148856..fc367d0 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
@@ -20,13 +20,11 @@
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
-import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
-import android.car.drivingstate.CarUxRestrictionsManager.UxRestrictionMode;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -65,14 +63,6 @@
     private static final String JSON_NAME_IDLING_RESTRICTIONS = "idling_restrictions";
     private static final String JSON_NAME_PARKED_RESTRICTIONS = "parked_restrictions";
     private static final String JSON_NAME_UNKNOWN_RESTRICTIONS = "unknown_restrictions";
-    private static final String JSON_NAME_PASSENGER_MOVING_RESTRICTIONS =
-            "passenger_moving_restrictions";
-    private static final String JSON_NAME_PASSENGER_IDLING_RESTRICTIONS =
-            "passenger_idling_restrictions";
-    private static final String JSON_NAME_PASSENGER_PARKED_RESTRICTIONS =
-            "passenger_parked_restrictions";
-    private static final String JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS =
-            "passenger_unknown_restrictions";
     private static final String JSON_NAME_REQ_OPT = "req_opt";
     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
     private static final String JSON_NAME_SPEED_RANGE = "speed_range";
@@ -82,10 +72,10 @@
     private final int mMaxContentDepth;
     private final int mMaxCumulativeContentItems;
     private final int mMaxStringLength;
-    private final Map<Integer, List<RestrictionsPerSpeedRange>> mPassengerUxRestrictions =
-            new ArrayMap<>(DRIVING_STATES.length);
-    private final Map<Integer, List<RestrictionsPerSpeedRange>> mBaselineUxRestrictions =
-            new ArrayMap<>(DRIVING_STATES.length);
+    /**
+     * Mapping of a restriction mode name to its restrictions.
+     */
+    private final Map<String, RestrictionModeContainer> mRestrictionModes = new ArrayMap<>();
 
     // null means the port is not configured. It should apply to default display.
     @Nullable
@@ -98,24 +88,23 @@
         mMaxCumulativeContentItems = builder.mMaxCumulativeContentItems;
         mMaxStringLength = builder.mMaxStringLength;
 
-        for (int drivingState : DRIVING_STATES) {
-            List<RestrictionsPerSpeedRange> baseline = new ArrayList<>();
-            for (RestrictionsPerSpeedRange r : builder.mBaselineUxRestrictions.get(drivingState)) {
-                baseline.add(r);
+        // make an immutable copy from the builder
+        for (Map.Entry<String, RestrictionModeContainer> entry :
+                builder.mRestrictionModes.entrySet()) {
+            String mode = entry.getKey();
+            RestrictionModeContainer container = new RestrictionModeContainer();
+            for (int drivingState : DRIVING_STATES) {
+                container.setRestrictionsForDriveState(drivingState,
+                        Collections.unmodifiableList(
+                                entry.getValue().getRestrictionsForDriveState(drivingState)));
             }
-            mBaselineUxRestrictions.put(drivingState, baseline);
-
-            List<RestrictionsPerSpeedRange> passenger = new ArrayList<>();
-            for (RestrictionsPerSpeedRange r : builder.mPassengerUxRestrictions.get(drivingState)) {
-                passenger.add(r);
-            }
-            mPassengerUxRestrictions.put(drivingState, passenger);
+            mRestrictionModes.put(mode, container);
         }
     }
 
     /**
      * Returns the restrictions for
-     * {@link UxRestrictionMode#UX_RESTRICTION_MODE_BASELINE}
+     * {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}
      * based on current driving state.
      *
      * @param drivingState Driving state.
@@ -131,25 +120,35 @@
      * Returns the restrictions based on current driving state and restriction mode.
      *
      * <p>Restriction mode allows a different set of restrictions to be applied in the same driving
-     * state. See values in {@link UxRestrictionMode}.
+     * state.
      *
      * @param drivingState Driving state.
      *                     See values in {@link CarDrivingStateEvent.CarDrivingState}.
      * @param currentSpeed Current speed in meter per second.
-     * @param mode Current UX Restriction mode.
+     * @param mode         Current UX Restriction mode.
      */
     public CarUxRestrictions getUxRestrictions(@CarDrivingState int drivingState,
-            float currentSpeed, @UxRestrictionMode int mode) {
+            float currentSpeed, @NonNull String mode) {
+        Objects.requireNonNull(mode, "mode must not be null");
         RestrictionsPerSpeedRange restriction = null;
-        if (mode == UX_RESTRICTION_MODE_PASSENGER) {
-            restriction = findUxRestrictionsInList(
-                    currentSpeed, mPassengerUxRestrictions.get(drivingState));
+        if (mRestrictionModes.containsKey(mode)) {
+            restriction = findUxRestrictionsInList(currentSpeed,
+                    mRestrictionModes.get(mode).getRestrictionsForDriveState(drivingState));
         }
+
         if (restriction == null) {
-            // Mode is baseline, or passenger mode does not specify restrictions for current driving
-            // state.
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG,
+                        String.format("No restrictions specified for (mode: %s, drive state: %s)",
+                                mode,
+                                drivingState));
+            }
+            // Either mode does not have any configuration or the mode does not have a configuration
+            // for the specific drive state. In either case, fall-back to baseline configuration.
             restriction = findUxRestrictionsInList(
-                    currentSpeed, mBaselineUxRestrictions.get(drivingState));
+                    currentSpeed,
+                    mRestrictionModes.get(UX_RESTRICTION_MODE_BASELINE)
+                            .getRestrictionsForDriveState(drivingState));
         }
 
         if (restriction == null) {
@@ -231,7 +230,8 @@
     /**
      * Writes current configuration as Json.
      */
-    public void writeJson(JsonWriter writer) throws IOException {
+    public void writeJson(@NonNull JsonWriter writer) throws IOException {
+        Objects.requireNonNull(writer, "writer must not be null");
         // We need to be lenient to accept infinity number (as max speed).
         writer.setLenient(true);
 
@@ -246,38 +246,29 @@
                 mMaxCumulativeContentItems);
         writer.name(JSON_NAME_MAX_STRING_LENGTH).value(mMaxStringLength);
 
+        for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
+            writer.name(entry.getKey());
+            writeRestrictionMode(writer, entry.getValue());
+        }
+
+        writer.endObject();
+    }
+
+    private void writeRestrictionMode(JsonWriter writer, RestrictionModeContainer container)
+            throws IOException {
+        writer.beginObject();
         writer.name(JSON_NAME_PARKED_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mBaselineUxRestrictions.get(DRIVING_STATE_PARKED));
+        writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_PARKED));
 
         writer.name(JSON_NAME_IDLING_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mBaselineUxRestrictions.get(DRIVING_STATE_IDLING));
+        writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_IDLING));
 
         writer.name(JSON_NAME_MOVING_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mBaselineUxRestrictions.get(DRIVING_STATE_MOVING));
+        writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_MOVING));
 
         writer.name(JSON_NAME_UNKNOWN_RESTRICTIONS);
         writeRestrictionsList(writer,
-                mBaselineUxRestrictions.get(DRIVING_STATE_UNKNOWN));
-
-        writer.name(JSON_NAME_PASSENGER_PARKED_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mPassengerUxRestrictions.get(DRIVING_STATE_PARKED));
-
-        writer.name(JSON_NAME_PASSENGER_IDLING_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mPassengerUxRestrictions.get(DRIVING_STATE_IDLING));
-
-        writer.name(JSON_NAME_PASSENGER_MOVING_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mPassengerUxRestrictions.get(DRIVING_STATE_MOVING));
-
-        writer.name(JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS);
-        writeRestrictionsList(writer,
-                mPassengerUxRestrictions.get(DRIVING_STATE_UNKNOWN));
-
+                container.getRestrictionsForDriveState(DRIVING_STATE_UNKNOWN));
         writer.endObject();
     }
 
@@ -319,12 +310,19 @@
     }
 
     /**
-     * Reads Json as UX restriction configuration.
+     * Reads Json as UX restriction configuration with the specified schema version.
+     *
+     * <p>Supports reading files persisted in multiple JSON schemas, including the pre-R version 1
+     * format, and the R format version 2.
      */
-    public static CarUxRestrictionsConfiguration readJson(JsonReader reader) throws IOException {
+    public static CarUxRestrictionsConfiguration readJson(@NonNull JsonReader reader,
+            int schemaVersion) throws IOException {
+        Objects.requireNonNull(reader, "reader must not be null");
         // We need to be lenient to accept infinity number (as max speed).
         reader.setLenient(true);
 
+        RestrictionConfigurationParser parser = createConfigurationParser(schemaVersion);
+
         Builder builder = new Builder();
         reader.beginObject();
         while (reader.hasNext()) {
@@ -346,6 +344,58 @@
                 case JSON_NAME_MAX_STRING_LENGTH:
                     builder.setMaxStringLength(reader.nextInt());
                     break;
+                default:
+                    parser.readJson(reader, name, builder);
+            }
+        }
+        reader.endObject();
+        return builder.build();
+    }
+
+    private static RestrictionConfigurationParser createConfigurationParser(int schemaVersion) {
+        switch (schemaVersion) {
+            case 1:
+                return new V1RestrictionConfigurationParser();
+            case 2:
+                return new V2RestrictionConfigurationParser();
+            default:
+                throw new IllegalArgumentException(
+                        "No parser supported for schemaVersion " + schemaVersion);
+        }
+    }
+
+    private interface RestrictionConfigurationParser {
+        /**
+         * Handle reading any information within a particular name and add data to the builder.
+         */
+        void readJson(JsonReader reader, String name, Builder builder) throws IOException;
+    }
+
+    private static class V2RestrictionConfigurationParser implements
+            RestrictionConfigurationParser {
+        @Override
+        public void readJson(JsonReader reader, String name, Builder builder) throws IOException {
+            readRestrictionsMode(reader, name, builder);
+        }
+    }
+
+    private static class V1RestrictionConfigurationParser implements
+            RestrictionConfigurationParser {
+
+        private static final String JSON_NAME_PASSENGER_MOVING_RESTRICTIONS =
+                "passenger_moving_restrictions";
+        private static final String JSON_NAME_PASSENGER_IDLING_RESTRICTIONS =
+                "passenger_idling_restrictions";
+        private static final String JSON_NAME_PASSENGER_PARKED_RESTRICTIONS =
+                "passenger_parked_restrictions";
+        private static final String JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS =
+                "passenger_unknown_restrictions";
+
+        private static final String PASSENGER_MODE_NAME_FOR_MIGRATION = "passenger";
+
+        @Override
+        public void readJson(JsonReader reader, String name, Builder builder) throws IOException {
+            switch (name) {
                 case JSON_NAME_PARKED_RESTRICTIONS:
                     readRestrictionsList(reader, DRIVING_STATE_PARKED,
                             UX_RESTRICTION_MODE_BASELINE, builder);
@@ -364,31 +414,54 @@
                     break;
                 case JSON_NAME_PASSENGER_PARKED_RESTRICTIONS:
                     readRestrictionsList(reader, DRIVING_STATE_PARKED,
-                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                            PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
                     break;
                 case JSON_NAME_PASSENGER_IDLING_RESTRICTIONS:
                     readRestrictionsList(reader, DRIVING_STATE_IDLING,
-                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                            PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
                     break;
                 case JSON_NAME_PASSENGER_MOVING_RESTRICTIONS:
                     readRestrictionsList(reader, DRIVING_STATE_MOVING,
-                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                            PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
                     break;
                 case JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS:
                     readRestrictionsList(reader, DRIVING_STATE_UNKNOWN,
-                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                            PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
                     break;
                 default:
                     Log.e(TAG, "Unknown name parsing json config: " + name);
                     reader.skipValue();
             }
         }
+    }
+
+    private static void readRestrictionsMode(JsonReader reader, String mode, Builder builder)
+            throws IOException {
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            switch (name) {
+                case JSON_NAME_PARKED_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_PARKED, mode, builder);
+                    break;
+                case JSON_NAME_IDLING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_IDLING, mode, builder);
+                    break;
+                case JSON_NAME_MOVING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_MOVING, mode, builder);
+                    break;
+                case JSON_NAME_UNKNOWN_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_UNKNOWN, mode, builder);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown name parsing restriction mode json config: " + name);
+            }
+        }
         reader.endObject();
-        return builder.build();
     }
 
     private static void readRestrictionsList(JsonReader reader, @CarDrivingState int drivingState,
-            @UxRestrictionMode int mode, Builder builder) throws IOException {
+            String mode, Builder builder) throws IOException {
         reader.beginArray();
         while (reader.hasNext()) {
             DrivingStateRestrictions drivingStateRestrictions = readRestrictions(reader);
@@ -443,9 +516,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPhysicalPort,
-                mMaxStringLength, mMaxCumulativeContentItems, mMaxContentDepth,
-                mBaselineUxRestrictions, mPassengerUxRestrictions);
+        return Objects.hash(mPhysicalPort, mMaxStringLength, mMaxCumulativeContentItems,
+                mMaxContentDepth, mRestrictionModes);
     }
 
     @Override
@@ -461,14 +533,14 @@
 
         return mPhysicalPort == other.mPhysicalPort
                 && hasSameParameters(other)
-                && mBaselineUxRestrictions.equals(other.mBaselineUxRestrictions)
-                && mPassengerUxRestrictions.equals(other.mPassengerUxRestrictions);
+                && mRestrictionModes.equals(other.mRestrictionModes);
     }
 
     /**
      * Compares {@code this} configuration object with {@code other} on restriction parameters.
      */
-    public boolean hasSameParameters(CarUxRestrictionsConfiguration other) {
+    public boolean hasSameParameters(@NonNull CarUxRestrictionsConfiguration other) {
+        Objects.requireNonNull(other, "other must not be null");
         return mMaxContentDepth == other.mMaxContentDepth
                 && mMaxCumulativeContentItems == other.mMaxCumulativeContentItems
                 && mMaxStringLength == other.mMaxStringLength;
@@ -477,17 +549,16 @@
     /**
      * Dump the driving state to UX restrictions mapping.
      */
-    public void dump(PrintWriter writer) {
+    public void dump(@NonNull PrintWriter writer) {
+        Objects.requireNonNull(writer, "writer must not be null");
         writer.println("Physical display port: " + mPhysicalPort);
 
-        writer.println("===========================================");
-        writer.println("Baseline mode UXR:");
-        writer.println("-------------------------------------------");
-        dumpRestrictions(writer, mBaselineUxRestrictions);
-
-        writer.println("Passenger mode UXR:");
-        writer.println("-------------------------------------------");
-        dumpRestrictions(writer, mPassengerUxRestrictions);
+        for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
+            writer.println("===========================================");
+            writer.println(entry.getKey() + " mode UXR:");
+            writer.println("-------------------------------------------");
+            dumpRestrictions(writer, entry.getValue().mDriveStateUxRestrictions);
+        }
 
         writer.println("Max String length: " + mMaxStringLength);
         writer.println("Max Cumulative Content Items: " + mMaxCumulativeContentItems);
@@ -531,7 +602,7 @@
     // Parcelable methods/fields.
 
     // Used by Parcel methods to ensure de/serialization order.
-    private static final int[] DRIVING_STATES = new int[] {
+    private static final int[] DRIVING_STATES = new int[]{
             DRIVING_STATE_UNKNOWN,
             DRIVING_STATE_PARKED,
             DRIVING_STATE_IDLING,
@@ -541,16 +612,16 @@
     public static final Parcelable.Creator<CarUxRestrictionsConfiguration> CREATOR =
             new Parcelable.Creator<CarUxRestrictionsConfiguration>() {
 
-        @Override
-        public CarUxRestrictionsConfiguration createFromParcel(Parcel source) {
-            return new CarUxRestrictionsConfiguration(source);
-        }
+                @Override
+                public CarUxRestrictionsConfiguration createFromParcel(Parcel source) {
+                    return new CarUxRestrictionsConfiguration(source);
+                }
 
-        @Override
-        public CarUxRestrictionsConfiguration[] newArray(int size) {
-            return new CarUxRestrictionsConfiguration[size];
-        }
-    };
+                @Override
+                public CarUxRestrictionsConfiguration[] newArray(int size) {
+                    return new CarUxRestrictionsConfiguration[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -558,16 +629,18 @@
     }
 
     private CarUxRestrictionsConfiguration(Parcel in) {
-        for (int drivingState : DRIVING_STATES) {
-            List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
-            in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
-            mBaselineUxRestrictions.put(drivingState, restrictions);
+        int modesCount = in.readInt();
+        for (int i = 0; i < modesCount; i++) {
+            String modeName = in.readString();
+            RestrictionModeContainer container = new RestrictionModeContainer();
+            for (int drivingState : DRIVING_STATES) {
+                List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
+                in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
+                container.setRestrictionsForDriveState(drivingState, restrictions);
+            }
+            mRestrictionModes.put(modeName, container);
         }
-        for (int drivingState : DRIVING_STATES) {
-            List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
-            in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
-            mPassengerUxRestrictions.put(drivingState, restrictions);
-        }
+
         boolean nullPhysicalPort = in.readBoolean();
         byte physicalPort = in.readByte();
         mPhysicalPort = nullPhysicalPort ? null : physicalPort;
@@ -579,11 +652,12 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        for (int drivingState : DRIVING_STATES) {
-            dest.writeTypedList(mBaselineUxRestrictions.get(drivingState));
-        }
-        for (int drivingState : DRIVING_STATES) {
-            dest.writeTypedList(mPassengerUxRestrictions.get(drivingState));
+        dest.writeInt(mRestrictionModes.size());
+        for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
+            dest.writeString(entry.getKey());
+            for (int drivingState : DRIVING_STATES) {
+                dest.writeTypedList(entry.getValue().getRestrictionsForDriveState(drivingState));
+            }
         }
         boolean nullPhysicalPort = mPhysicalPort == null;
         dest.writeBoolean(nullPhysicalPort);
@@ -626,16 +700,10 @@
         private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
         private int mMaxStringLength = UX_RESTRICTIONS_UNKNOWN;
 
-        public Map<Integer, List<RestrictionsPerSpeedRange>> mPassengerUxRestrictions =
-                new ArrayMap<>(DRIVING_STATES.length);
-        public Map<Integer, List<RestrictionsPerSpeedRange>> mBaselineUxRestrictions =
-                new ArrayMap<>(DRIVING_STATES.length);
+        public final Map<String, RestrictionModeContainer> mRestrictionModes = new ArrayMap<>();
 
         public Builder() {
-            for (int drivingState : DRIVING_STATES) {
-                mBaselineUxRestrictions.put(drivingState, new ArrayList<>());
-                mPassengerUxRestrictions.put(drivingState, new ArrayList<>());
-            }
+            mRestrictionModes.put(UX_RESTRICTION_MODE_BASELINE, new RestrictionModeContainer());
         }
 
         /**
@@ -665,16 +733,16 @@
         /**
          * Sets UX restrictions with speed range.
          *
-         * @param drivingState Restrictions will be set for this Driving state.
-         *                     See constants in {@link CarDrivingStateEvent}.
-         * @param speedRange If set, restrictions will only apply when current speed is within
-         *                   the range. Only {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}
-         *                   supports speed range. {@code null} implies the full speed range,
-         *                   i.e. zero to {@link SpeedRange#MAX_SPEED}.
+         * @param drivingState         Restrictions will be set for this Driving state.
+         *                             See constants in {@link CarDrivingStateEvent}.
+         * @param speedRange           If set, restrictions will only apply when current speed is
+         *                             within the range. Only
+         *                             {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}
+         *                             supports speed range. {@code null} implies the full speed
+         *                             range, i.e. zero to {@link SpeedRange#MAX_SPEED}.
          * @param requiresOptimization Whether distraction optimization (DO) is required for this
          *                             driving state.
-         * @param restrictions See constants in {@link CarUxRestrictions}.
-         *
+         * @param restrictions         See constants in {@link CarUxRestrictions}.
          * @deprecated Use {@link #setUxRestrictions(int, DrivingStateRestrictions)} instead.
          */
         @Deprecated
@@ -690,10 +758,9 @@
         /**
          * Sets UX restriction.
          *
-         * @param drivingState Restrictions will be set for this Driving state.
-         *                     See constants in {@link CarDrivingStateEvent}.
+         * @param drivingState             Restrictions will be set for this Driving state.
+         *                                 See constants in {@link CarDrivingStateEvent}.
          * @param drivingStateRestrictions Restrictions to set.
-         *
          * @return This builder object for method chaining.
          */
         public Builder setUxRestrictions(
@@ -705,22 +772,13 @@
                         "Non-moving driving state should not specify speed range.");
             }
 
-            List<RestrictionsPerSpeedRange> restrictions;
-            switch (drivingStateRestrictions.mMode) {
-                case UX_RESTRICTION_MODE_BASELINE:
-                    restrictions = mBaselineUxRestrictions.get(drivingState);
-                    break;
-                case UX_RESTRICTION_MODE_PASSENGER:
-                    restrictions = mPassengerUxRestrictions.get(drivingState);
-                    break;
-                default:
-                    String mode = CarUxRestrictionsManager.modeToString(
-                            drivingStateRestrictions.mMode);
-                    throw new IllegalArgumentException("Unrecognized restriction mode " + mode);
-            }
-            restrictions.add(new RestrictionsPerSpeedRange(
-                    drivingStateRestrictions.mMode, drivingStateRestrictions.mReqOpt,
-                    drivingStateRestrictions.mRestrictions, speedRange));
+            RestrictionModeContainer container = mRestrictionModes.computeIfAbsent(
+                    drivingStateRestrictions.mMode, mode -> new RestrictionModeContainer());
+
+            container.getRestrictionsForDriveState(drivingState).add(
+                    new RestrictionsPerSpeedRange(
+                            drivingStateRestrictions.mMode, drivingStateRestrictions.mReqOpt,
+                            drivingStateRestrictions.mRestrictions, speedRange));
             return this;
         }
 
@@ -757,15 +815,22 @@
             addDefaultRestrictionsToBaseline();
 
             validateBaselineModeRestrictions();
-            validatePassengerModeRestrictions();
+            for (String mode : mRestrictionModes.keySet()) {
+                if (UX_RESTRICTION_MODE_BASELINE.equals(mode)) {
+                    continue;
+                }
+                validateModeRestrictions(mode);
+            }
 
             return new CarUxRestrictionsConfiguration(this);
         }
 
         private void addDefaultRestrictionsToBaseline() {
+            RestrictionModeContainer container = mRestrictionModes.get(
+                    UX_RESTRICTION_MODE_BASELINE);
             for (int drivingState : DRIVING_STATES) {
                 List<RestrictionsPerSpeedRange> restrictions =
-                        mBaselineUxRestrictions.get(drivingState);
+                        container.getRestrictionsForDriveState(drivingState);
                 if (restrictions.size() == 0) {
                     Log.i(TAG, "Using default restrictions for driving state: "
                             + getDrivingStateName(drivingState));
@@ -776,9 +841,11 @@
         }
 
         private void validateBaselineModeRestrictions() {
+            RestrictionModeContainer container = mRestrictionModes.get(
+                    UX_RESTRICTION_MODE_BASELINE);
             for (int drivingState : DRIVING_STATES) {
                 List<RestrictionsPerSpeedRange> restrictions =
-                        mBaselineUxRestrictions.get(drivingState);
+                        container.getRestrictionsForDriveState(drivingState);
                 if (drivingState != DRIVING_STATE_MOVING) {
                     // Note: For non-moving state, setUxRestrictions() rejects UxRestriction with
                     // speed range, so we don't check here.
@@ -809,13 +876,17 @@
             }
         }
 
-        private void validatePassengerModeRestrictions() {
-            List<RestrictionsPerSpeedRange> passengerMovingRestrictions =
-                    mPassengerUxRestrictions.get(DRIVING_STATE_MOVING);
-            Collections.sort(passengerMovingRestrictions,
+        private void validateModeRestrictions(String mode) {
+            if (!mRestrictionModes.containsKey(mode)) {
+                return;
+            }
+            RestrictionModeContainer container = mRestrictionModes.get(mode);
+            List<RestrictionsPerSpeedRange> movingRestrictions =
+                    container.getRestrictionsForDriveState(DRIVING_STATE_MOVING);
+            Collections.sort(movingRestrictions,
                     Comparator.comparing(RestrictionsPerSpeedRange::getSpeedRange));
 
-            validateContinuousSpeedRange(passengerMovingRestrictions);
+            validateContinuousSpeedRange(movingRestrictions);
         }
 
         /**
@@ -909,7 +980,7 @@
                 mMaxSpeed = maxSpeed;
             }
 
-             /**
+            /**
              * Return if the given speed is in the range of [minSpeed, maxSpeed).
              *
              * @param speed Speed to check
@@ -965,16 +1036,15 @@
      * These UX restrictions can also specified to be only applicable to certain speed range and
      * restriction mode.
      *
-     * @see UxRestrictionMode
-     * @see Builder.SpeedRange
-     *
      * @hide
+     * @see Builder.SpeedRange
      */
     public static final class DrivingStateRestrictions {
-        private int mMode = UX_RESTRICTION_MODE_BASELINE;
+        private String mMode = UX_RESTRICTION_MODE_BASELINE;
         private boolean mReqOpt = true;
         private int mRestrictions = CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
-        @Nullable private Builder.SpeedRange mSpeedRange;
+        @Nullable
+        private Builder.SpeedRange mSpeedRange;
 
         /**
          * Sets whether Distraction Optimization (DO) is required. Defaults to {@code true}.
@@ -999,8 +1069,8 @@
          * Sets restriction mode to apply to.
          * Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
          */
-        public DrivingStateRestrictions setMode(@UxRestrictionMode int mode) {
-            mMode = mode;
+        public DrivingStateRestrictions setMode(@NonNull String mode) {
+            mMode = Objects.requireNonNull(mode, "mode must not be null");
             return this;
         }
 
@@ -1016,7 +1086,7 @@
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("Mode: ").append(CarUxRestrictionsManager.modeToString(mMode))
+                    .append("Mode: ").append(mMode)
                     .append(". Requires DO? ").append(mReqOpt)
                     .append(". Restrictions: ").append(Integer.toBinaryString(mRestrictions))
                     .append(". SpeedRange: ")
@@ -1030,8 +1100,7 @@
      * Speed range is valid only for the {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}.
      */
     private static final class RestrictionsPerSpeedRange implements Parcelable {
-        @UxRestrictionMode
-        final int mMode;
+        final String mMode;
         final boolean mReqOpt;
         final int mRestrictions;
         @Nullable
@@ -1041,13 +1110,13 @@
             this(UX_RESTRICTION_MODE_BASELINE, reqOpt, restrictions, null);
         }
 
-        RestrictionsPerSpeedRange(@UxRestrictionMode int mode, boolean reqOpt, int restrictions,
+        RestrictionsPerSpeedRange(@NonNull String mode, boolean reqOpt, int restrictions,
                 @Nullable Builder.SpeedRange speedRange) {
             if (!reqOpt && restrictions != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
                 throw new IllegalArgumentException(
                         "Driving optimization is not required but UX restrictions is required.");
             }
-            mMode = mode;
+            mMode = Objects.requireNonNull(mode, "mode must not be null");
             mReqOpt = reqOpt;
             mRestrictions = restrictions;
             mSpeedRange = speedRange;
@@ -1071,7 +1140,7 @@
                 return false;
             }
             RestrictionsPerSpeedRange other = (RestrictionsPerSpeedRange) obj;
-            return mMode == other.mMode
+            return Objects.equals(mMode, other.mMode)
                     && mReqOpt == other.mReqOpt
                     && mRestrictions == other.mRestrictions
                     && Objects.equals(mSpeedRange, other.mSpeedRange);
@@ -1080,7 +1149,7 @@
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("[Mode is ").append(CarUxRestrictionsManager.modeToString(mMode))
+                    .append("[Mode is ").append(mMode)
                     .append("; Requires DO? ").append(mReqOpt)
                     .append("; Restrictions: ").append(Integer.toBinaryString(mRestrictions))
                     .append("; Speed range: ")
@@ -1110,7 +1179,7 @@
         }
 
         protected RestrictionsPerSpeedRange(Parcel in) {
-            mMode = in.readInt();
+            mMode = in.readString();
             mReqOpt = in.readBoolean();
             mRestrictions = in.readInt();
             // Whether speed range is specified.
@@ -1125,7 +1194,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(mMode);
+            dest.writeString(mMode);
             dest.writeBoolean(mReqOpt);
             dest.writeInt(mRestrictions);
             // Whether speed range is specified.
@@ -1136,4 +1205,55 @@
             }
         }
     }
+
+    /**
+     * All the restriction configurations for a particular mode.
+     */
+    private static final class RestrictionModeContainer {
+        /**
+         * Mapping from drive state to the list of applicable restrictions.
+         */
+        private final Map<Integer, List<RestrictionsPerSpeedRange>> mDriveStateUxRestrictions =
+                new ArrayMap<>(DRIVING_STATES.length);
+
+        RestrictionModeContainer() {
+            for (int drivingState : DRIVING_STATES) {
+                mDriveStateUxRestrictions.put(drivingState, new ArrayList<>());
+            }
+        }
+
+        /**
+         * Returns the restrictions for a particular drive state.
+         */
+        @NonNull
+        List<RestrictionsPerSpeedRange> getRestrictionsForDriveState(
+                @CarDrivingState int driveState) {
+            // Guaranteed not to be null since a container is initialized with empty lists for
+            // each drive state in the constructor.
+            return mDriveStateUxRestrictions.get(driveState);
+        }
+
+        void setRestrictionsForDriveState(@CarDrivingState int driveState,
+                @NonNull List<RestrictionsPerSpeedRange> restrictions) {
+            Objects.requireNonNull(restrictions, "null restrictions are not allows");
+            mDriveStateUxRestrictions.put(driveState, restrictions);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof RestrictionModeContainer)) {
+                return false;
+            }
+            RestrictionModeContainer container = (RestrictionModeContainer) obj;
+            return Objects.equals(mDriveStateUxRestrictions, container.mDriveStateUxRestrictions);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDriveStateUxRestrictions);
+        }
+    }
 }
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index be194b8..1557e6e 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -16,7 +16,6 @@
 
 package android.car.drivingstate;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -32,11 +31,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * API to register and get the User Experience restrictions imposed based on the car's driving
@@ -53,31 +51,7 @@
      *
      * @hide
      */
-    public static final int UX_RESTRICTION_MODE_BASELINE = 0;
-    /**
-     * Passenger restriction mode uses UX restrictions for {@link #UX_RESTRICTION_MODE_PASSENGER},
-     * set through {@link CarUxRestrictionsConfiguration.Builder.UxRestrictions#setMode(int)}.
-     *
-     * <p>If a new {@link CarUxRestrictions} is available upon mode transition, it'll be immediately
-     * dispatched to listeners.
-     *
-     * <p>If passenger mode restrictions is not configured for current driving state, it will fall
-     * back to {@link #UX_RESTRICTION_MODE_BASELINE}.
-     *
-     * <p>Caller are responsible for determining and executing the criteria for entering and exiting
-     * this mode. Exiting by setting mode to {@link #UX_RESTRICTION_MODE_BASELINE}.
-     *
-     * @hide
-     */
-    public static final int UX_RESTRICTION_MODE_PASSENGER = 1;
-
-    /** @hide */
-    @IntDef(prefix = { "UX_RESTRICTION_MODE_" }, value = {
-            UX_RESTRICTION_MODE_BASELINE,
-            UX_RESTRICTION_MODE_PASSENGER
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface UxRestrictionMode {}
+    public static final String UX_RESTRICTION_MODE_BASELINE = "baseline";
 
     private int mDisplayId = Display.INVALID_DISPLAY;
     private final ICarUxRestrictionsManager mUxRService;
@@ -187,7 +161,6 @@
      *
      * @param configs Map of display Id to UX restrictions configurations to be persisted.
      * @return {@code true} if input config was successfully saved; {@code false} otherwise.
-     *
      * @hide
      */
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
@@ -225,10 +198,22 @@
     /**
      * Sets restriction mode. Returns {@code true} if the operation succeeds.
      *
+     * <p>The default mode is {@link #UX_RESTRICTION_MODE_BASELINE}.
+     *
+     * <p>If a new {@link CarUxRestrictions} is available upon mode transition, it'll
+     * be immediately dispatched to listeners.
+     *
+     * <p>If the given mode is not configured for current driving state, it
+     * will fall back to the default value.
+     *
+     * <p>If a configuration was set for a passenger mode before an upgrade to Android R, that
+     * passenger configuration is now called "passenger".
+     *
      * @hide
      */
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
-    public boolean setRestrictionMode(@UxRestrictionMode int mode) {
+    public boolean setRestrictionMode(@NonNull String mode) {
+        Objects.requireNonNull(mode, "mode must not be null");
         try {
             return mUxRService.setRestrictionMode(mode);
         } catch (RemoteException e) {
@@ -239,11 +224,16 @@
     /**
      * Returns the current restriction mode.
      *
+     * <p>The default mode is {@link #UX_RESTRICTION_MODE_BASELINE}.
+     *
+     * <p>If a configuration was set for a passenger mode before an upgrade to Android R, that
+     * passenger configuration is now called "passenger".
+     *
      * @hide
      */
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
-    @UxRestrictionMode
-    public int getRestrictionMode() {
+    @NonNull
+    public String getRestrictionMode() {
         try {
             return mUxRService.getRestrictionMode();
         } catch (RemoteException e) {
@@ -259,7 +249,6 @@
      *
      * @param config UX restrictions configuration to be persisted.
      * @return {@code true} if input config was successfully saved; {@code false} otherwise.
-     *
      * @hide
      */
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
@@ -278,7 +267,6 @@
      * This methods is only for test purpose, please do not use in production.
      *
      * @return current staged configuration, {@code null} if it's not available
-     *
      * @hide
      */
     @Nullable
@@ -295,7 +283,6 @@
      * Gets the current configurations.
      *
      * @return current configurations that is in effect.
-     *
      * @hide
      */
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
@@ -308,20 +295,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static String modeToString(@UxRestrictionMode int mode) {
-        switch (mode) {
-            case UX_RESTRICTION_MODE_BASELINE:
-                return "baseline";
-            case UX_RESTRICTION_MODE_PASSENGER:
-                return "passenger";
-            default:
-                throw new IllegalArgumentException("Unrecognized restriction mode " + mode);
-        }
-    }
-
-    /**
      * Class that implements the listener interface and gets called back from the
      * {@link com.android.car.CarDrivingStateService} across the binder interface.
      */
diff --git a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
index 7f3a6e9..8c9afd7 100644
--- a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
+++ b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
@@ -34,6 +34,8 @@
     boolean saveUxRestrictionsConfigurationForNextBoot(in List<CarUxRestrictionsConfiguration> configs) = 3;
     List<CarUxRestrictionsConfiguration> getStagedConfigs() = 4;
     List<CarUxRestrictionsConfiguration> getConfigs() = 5;
-    boolean setRestrictionMode(int mode) = 6;
-    int getRestrictionMode() = 7;
+    // 6 removed. Do not use - boolean setRestrictionMode(int mode) = 6;
+    // 7 removed. Do not use - int getRestrictionMode() = 7;
+    boolean setRestrictionMode(String mode) = 10;
+    String getRestrictionMode() = 11;
 }
diff --git a/service/res/values/attrs.xml b/service/res/values/attrs.xml
index eec51a8..e026cae 100644
--- a/service/res/values/attrs.xml
+++ b/service/res/values/attrs.xml
@@ -99,14 +99,11 @@
             <flag name="no_voice_transcription" value="256"/>
             <flag name="fully_restricted" value="511"/>
         </attr>
-        <!-- UX restrictions service supports returning different sets of UX restrictions for
-        the same driving state, through configurations for each "mode". -->
-        <attr name="mode">
-            <!-- Default mode. -->
-            <flag name="baseline" value="0"/>
-            <!-- Mode for passenger to interact with system. -->
-            <flag name="passenger" value="1"/>
-        </attr>
+        <!-- UX restrictions service supports returning different sets of UX restrictions for the
+             same driving state, through configurations for each "mode". These modes can be
+             specified by any string name, for example, "passenger". "baseline" is the default
+             value. -->
+        <attr name="mode" format="string"/>
     </declare-styleable>
 
     <!-- 2. Some of UX restrictions can be parametrized. -->
diff --git a/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java b/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
index 0a37491..15e71d1 100644
--- a/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
+++ b/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
@@ -270,7 +270,7 @@
         }
 
         int restrictions = UX_RESTRICTIONS_UNKNOWN;
-        int restrictionMode = UX_RESTRICTION_MODE_BASELINE;
+        String restrictionMode = UX_RESTRICTION_MODE_BASELINE;
         boolean requiresOpt = true;
         while (RESTRICTIONS.equals(parser.getName())
                 && parser.getEventType() == XmlResourceParser.START_TAG) {
@@ -281,12 +281,14 @@
                     CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
             requiresOpt = a.getBoolean(
                     R.styleable.UxRestrictions_Restrictions_requiresDistractionOptimization, true);
-            restrictionMode = a.getInt(
-                    R.styleable.UxRestrictions_Restrictions_mode, UX_RESTRICTION_MODE_BASELINE);
+            restrictionMode = a.getString(R.styleable.UxRestrictions_Restrictions_mode);
 
             a.recycle();
             parser.next();
         }
+        if (restrictionMode == null) {
+            restrictionMode = UX_RESTRICTION_MODE_BASELINE;
+        }
         return new DrivingStateRestrictions()
                 .setDistractionOptimizationRequired(requiresOpt)
                 .setRestrictions(restrictions)
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index bb49ff2..0ea757f 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -24,6 +24,7 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
@@ -51,6 +52,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.JsonReader;
+import android.util.JsonToken;
 import android.util.JsonWriter;
 import android.util.Log;
 import android.util.Slog;
@@ -70,6 +72,8 @@
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -78,6 +82,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -86,7 +91,7 @@
  * <p>
  * <h1>UX Restrictions Configuration</h1>
  * When this service starts, it will first try reading the configuration set through
- * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)}.
+ * {@link #saveUxRestrictionsConfigurationForNextBoot(List)}.
  * If one is not available, it will try reading the configuration saved in
  * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will
  * fall back to a hard-coded configuration.
@@ -104,6 +109,17 @@
     private static final float SPEED_NOT_AVAILABLE = -1.0F;
     private static final byte DEFAULT_PORT = 0;
 
+    private static final int UNKNOWN_JSON_SCHEMA_VERSION = -1;
+    private static final int JSON_SCHEMA_VERSION_V1 = 1;
+    private static final int JSON_SCHEMA_VERSION_V2 = 2;
+
+    @IntDef({UNKNOWN_JSON_SCHEMA_VERSION, JSON_SCHEMA_VERSION_V1, JSON_SCHEMA_VERSION_V2})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface JsonSchemaVersion {}
+
+    private static final String JSON_NAME_SCHEMA_VERSION = "schema_version";
+    private static final String JSON_NAME_RESTRICTIONS = "restrictions";
+
     @VisibleForTesting
     static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json";
     @VisibleForTesting
@@ -127,8 +143,7 @@
     private Map<Byte, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations;
     private Map<Byte, CarUxRestrictions> mCurrentUxRestrictions;
 
-    @CarUxRestrictionsManager.UxRestrictionMode
-    private int mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
+    private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
 
     // Flag to disable broadcasting UXR changes - for development purposes
     @GuardedBy("this")
@@ -183,8 +198,7 @@
      *
      * <p>Reads config from the following sources in order:
      * <ol>
-     * <li>saved config set by
-     * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)};
+     * <li>saved config set by {@link #saveUxRestrictionsConfigurationForNextBoot(List)};
      * <li>XML resource config from {@code R.xml.car_ux_restrictions_map};
      * <li>hardcoded default config.
      * </ol>
@@ -434,24 +448,24 @@
      *
      * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
      *
-     * @param mode See values in {@link CarUxRestrictionsManager.UxRestrictionMode}.
+     * @param mode the restriction mode
      * @return {@code true} if mode was successfully changed; {@code false} otherwise.
      * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
      * @see CarUxRestrictionsConfiguration.Builder
      */
     @Override
-    public synchronized boolean setRestrictionMode(
-            @CarUxRestrictionsManager.UxRestrictionMode int mode) {
+    public synchronized boolean setRestrictionMode(@NonNull String mode) {
         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
+        Objects.requireNonNull(mode, "mode must not be null");
 
-        if (mRestrictionMode == mode) {
+        if (mRestrictionMode.equals(mode)) {
             return true;
         }
 
         addTransitionLog(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
                 "Restriction mode");
         mRestrictionMode = mode;
-        logd("Set restriction mode to: " + CarUxRestrictionsManager.modeToString(mode));
+        logd("Set restriction mode to: " + mode);
 
         handleDispatchUxRestrictions(
                 mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
@@ -459,8 +473,8 @@
     }
 
     @Override
-    @CarUxRestrictionsManager.UxRestrictionMode
-    public synchronized int getRestrictionMode() {
+    @NonNull
+    public synchronized String getRestrictionMode() {
         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
 
         return mRestrictionMode;
@@ -483,11 +497,15 @@
         }
         try (JsonWriter jsonWriter = new JsonWriter(
                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
+            jsonWriter.beginObject();
+            jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2);
+            jsonWriter.name(JSON_NAME_RESTRICTIONS);
             jsonWriter.beginArray();
             for (CarUxRestrictionsConfiguration config : configs) {
                 config.writeJson(jsonWriter);
             }
             jsonWriter.endArray();
+            jsonWriter.endObject();
         } catch (IOException e) {
             Log.e(TAG, "Could not persist config", e);
             stagedFile.failWrite(fos);
@@ -504,15 +522,26 @@
             return null;
         }
 
+        // Take one pass at the file to check the version and then a second pass to read the
+        // contents. We could assess the version and read in one pass, but we're preferring
+        // clarity over complexity here.
+        int schemaVersion = readFileSchemaVersion(file);
+
         AtomicFile configFile = new AtomicFile(file);
         try (JsonReader reader = new JsonReader(
                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
-            reader.beginArray();
-            while (reader.hasNext()) {
-                configs.add(CarUxRestrictionsConfiguration.readJson(reader));
+            switch (schemaVersion) {
+                case JSON_SCHEMA_VERSION_V1:
+                    readV1Json(reader, configs);
+                    break;
+                case JSON_SCHEMA_VERSION_V2:
+                    readV2Json(reader, configs);
+                    break;
+                default:
+                    Log.e(TAG, "Unable to parse schema for version " + schemaVersion);
             }
-            reader.endArray();
+
             return configs;
         } catch (IOException e) {
             Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
@@ -520,6 +549,68 @@
         return null;
     }
 
+    private void readV1Json(JsonReader reader,
+            List<CarUxRestrictionsConfiguration> configs) throws IOException {
+        readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1);
+    }
+
+    private void readV2Json(JsonReader reader,
+            List<CarUxRestrictionsConfiguration> configs) throws IOException {
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            switch (name) {
+                case JSON_NAME_RESTRICTIONS:
+                    readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2);
+                    break;
+                default:
+                    reader.skipValue();
+            }
+        }
+        reader.endObject();
+    }
+
+    private int readFileSchemaVersion(File file) {
+        AtomicFile configFile = new AtomicFile(file);
+        try (JsonReader reader = new JsonReader(
+                new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
+            List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
+            if (reader.peek() == JsonToken.BEGIN_ARRAY) {
+                // only schema V1 beings with an array - no need to keep reading
+                reader.close();
+                return JSON_SCHEMA_VERSION_V1;
+            } else {
+                reader.beginObject();
+                while (reader.hasNext()) {
+                    String name = reader.nextName();
+                    switch (name) {
+                        case JSON_NAME_SCHEMA_VERSION:
+                            int schemaVersion = reader.nextInt();
+                            // got the version, no need to continue reading
+                            reader.close();
+                            return schemaVersion;
+                        default:
+                            reader.skipValue();
+                    }
+                }
+                reader.endObject();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
+        }
+        return UNKNOWN_JSON_SCHEMA_VERSION;
+    }
+
+    private void readRestrictionsArray(JsonReader reader,
+            List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)
+            throws IOException {
+        reader.beginArray();
+        while (reader.hasNext()) {
+            configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion));
+        }
+        reader.endArray();
+    }
+
     /**
      * Enable/disable UX restrictions change broadcast blocking.
      * Setting this to true will stop broadcasts of UX restriction change to listeners.
@@ -940,7 +1031,8 @@
                 .build();
     }
 
-    private void addTransitionLog(String name, int from, int to, long timestamp, String extra) {
+    private void addTransitionLog(String name, String from, String to, long timestamp,
+            String extra) {
         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
             mTransitionLogs.remove();
         }
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 2b468b7..41ab67b 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -142,18 +142,18 @@
      */
     public static class TransitionLog {
         private String mServiceName; // name of the service or tag
-        private int mFromState; // old state
-        private int mToState; // new state
+        private Object mFromState; // old state
+        private Object mToState; // new state
         private long mTimestampMs; // System.currentTimeMillis()
         private String mExtra; // Additional information as a String
 
-        public TransitionLog(String name, int fromState, int toState, long timestamp,
+        public TransitionLog(String name, Object fromState, Object toState, long timestamp,
                 String extra) {
             this(name, fromState, toState, timestamp);
             mExtra = extra;
         }
 
-        public TransitionLog(String name, int fromState, int toState, long timeStamp) {
+        public TransitionLog(String name, Object fromState, Object toState, long timeStamp) {
             mServiceName = name;
             mFromState = fromState;
             mToState = toState;
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
index 89f3349..f4eff56 100644
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
@@ -19,7 +19,6 @@
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
-import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
 import android.app.AlertDialog;
 import android.car.Car;
@@ -50,6 +49,7 @@
     public static final String TAG = "UxRDemo";
 
     private static final String DIALOG_FRAGMENT_TAG = "dialog_fragment_tag";
+    private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
 
     private Car mCar;
     private CarDrivingStateManager mCarDrivingStateManager;
@@ -187,7 +187,7 @@
             return;
         }
 
-        int mode = mCarUxRestrictionsManager.getRestrictionMode();
+        String mode = mCarUxRestrictionsManager.getRestrictionMode();
         switch (mode) {
             case UX_RESTRICTION_MODE_BASELINE:
                 mCarUxRestrictionsManager.setRestrictionMode(UX_RESTRICTION_MODE_PASSENGER);
@@ -218,7 +218,7 @@
                                 .setRestrictions(baseline))
                 .setUxRestrictions(DRIVING_STATE_MOVING,
                         new DrivingStateRestrictions()
-                                .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER)
                                 .setDistractionOptimizationRequired(passenger != 0)
                                 .setRestrictions(passenger))
                 .setUxRestrictions(DRIVING_STATE_IDLING,
@@ -227,7 +227,7 @@
                                 .setRestrictions(baseline))
                 .setUxRestrictions(DRIVING_STATE_IDLING,
                         new DrivingStateRestrictions()
-                                .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER)
                                 .setDistractionOptimizationRequired(passenger != 0)
                                 .setRestrictions(passenger))
                 .build();
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
index a040442..030b133 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
@@ -19,12 +19,12 @@
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO;
 import static android.car.drivingstate.CarUxRestrictionsConfiguration.Builder.SpeedRange.MAX_SPEED;
 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
-import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
@@ -38,11 +38,14 @@
 
 import junit.framework.TestCase;
 
+import org.junit.Test;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.io.StringReader;
 
 /**
  * Unit test for UXR config and its subclasses.
@@ -50,6 +53,9 @@
 @SmallTest
 public class CarUxRestrictionsConfigurationTest extends TestCase {
 
+    private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
+
+    @Test
     // This test verifies the expected way to build config would succeed.
     public void testConstruction() {
         new Builder().build();
@@ -124,13 +130,13 @@
     public void testBuilderValidation_PassengerModeNoSpeedRangeOverlap() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
-                    .setDistractionOptimizationRequired(true)
-                    .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
-                    .setSpeedRange(new Builder.SpeedRange(1f, 2f)));
+                .setDistractionOptimizationRequired(true)
+                .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                .setSpeedRange(new Builder.SpeedRange(1f, 2f)));
         builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
-                    .setDistractionOptimizationRequired(true)
-                    .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
-                    .setSpeedRange(new Builder.SpeedRange(1f)));
+                .setDistractionOptimizationRequired(true)
+                .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                .setSpeedRange(new Builder.SpeedRange(1f)));
         try {
             builder.build();
             fail();
@@ -294,7 +300,7 @@
                 new Builder.SpeedRange(1f);
         Builder.SpeedRange s2 =
                 new Builder.SpeedRange(1f);
-        assertTrue(s1.compareTo(s2) == 0);
+        assertEquals(0, s1.compareTo(s2));
     }
 
     public void testSpeedRangeComparison_SameMinDifferentMax() {
@@ -319,20 +325,20 @@
         Builder.SpeedRange s1, s2;
 
         s1 = new Builder.SpeedRange(0f);
-        assertTrue(s1.equals(s1));
+        assertEquals(s1, s1);
 
         s1 = new Builder.SpeedRange(1f);
         s2 = new Builder.SpeedRange(1f);
-        assertTrue(s1.compareTo(s2) == 0);
-        assertTrue(s1.equals(s2));
+        assertEquals(0, s1.compareTo(s2));
+        assertEquals(s1, s2);
 
         s1 = new Builder.SpeedRange(0f, 1f);
         s2 = new Builder.SpeedRange(0f, 1f);
-        assertTrue(s1.equals(s2));
+        assertEquals(s1, s2);
 
         s1 = new Builder.SpeedRange(0f, MAX_SPEED);
         s2 = new Builder.SpeedRange(0f, MAX_SPEED);
-        assertTrue(s1.equals(s2));
+        assertEquals(s1, s2);
 
         s1 = new Builder.SpeedRange(0f);
         s2 = new Builder.SpeedRange(1f);
@@ -347,7 +353,7 @@
         CarUxRestrictionsConfiguration config =
                 new Builder().build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
     public void testJsonSerialization_RestrictionParameters() {
@@ -357,7 +363,7 @@
                 .setMaxContentDepth(1)
                 .build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
     public void testJsonSerialization_NonMovingStateRestrictions() {
@@ -365,7 +371,7 @@
                 .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
                 .build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
     public void testJsonSerialization_MovingStateNoSpeedRange() {
@@ -373,7 +379,7 @@
                 .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
                 .build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
     public void testJsonSerialization_MovingStateWithSpeedRange() {
@@ -388,7 +394,7 @@
                         .setSpeedRange(new Builder.SpeedRange(5f, MAX_SPEED)))
                 .build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
     public void testJsonSerialization_UxRestrictionMode() {
@@ -409,11 +415,79 @@
                         .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
                 .build();
 
-        verifyConfigThroughJsonSerialization(config);
+        verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
+    }
+
+    @Test
+    public void testJsonSerialization_ReadsV1() throws Exception {
+        String v1LegacyJsonFormat = "{\"physical_port\":1,\"max_content_depth\":2,"
+                + "\"max_cumulative_content_items\":20,\"max_string_length\":21,"
+                + "\"parked_restrictions\":[{\"req_opt\":false,\"restrictions\":0}],"
+                + "\"idling_restrictions\":[{\"req_opt\":true,\"restrictions\":7}],"
+                + "\"moving_restrictions\":[{\"req_opt\":true,\"restrictions\":8}],"
+                + "\"unknown_restrictions\":[{\"req_opt\":true,\"restrictions\":511}],"
+                + "\"passenger_parked_restrictions\":[{\"req_opt\":false,\"restrictions\":0}],"
+                + "\"passenger_idling_restrictions\":[{\"req_opt\":true,\"restrictions\":56}],"
+                + "\"passenger_moving_restrictions\":[{\"req_opt\":true,\"restrictions\":57}],"
+                + "\"passenger_unknown_restrictions\":[{\"req_opt\":true,\"restrictions\":510}]}";
+        CarUxRestrictionsConfiguration expectedConfig = new Builder()
+                .setPhysicalPort((byte) 1)
+                .setMaxContentDepth(2)
+                .setMaxCumulativeContentItems(20)
+                .setMaxStringLength(21)
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_IDLING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(7)
+                        .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(8)
+                        .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_UNKNOWN, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(511)
+                        .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_IDLING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(56)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(57)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_UNKNOWN, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(510)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .build();
+
+        CarUxRestrictionsConfiguration deserialized = CarUxRestrictionsConfiguration.readJson(
+                new JsonReader(new StringReader(v1LegacyJsonFormat)), /* schemaVersion= */ 1);
+        assertEquals(expectedConfig, deserialized);
+    }
+
+
+    @Test
+    public void testJsonSerialization_ReadUnsupportedVersion_ThrowsException() throws Exception {
+        int unsupportedVersion = -1;
+        try {
+            CarUxRestrictionsConfiguration deserialized = CarUxRestrictionsConfiguration.readJson(
+                    new JsonReader(new StringReader("")), unsupportedVersion);
+        } catch (IllegalArgumentException e) {
+            // expected exception
+        }
     }
 
     public void testDump() {
-        CarUxRestrictionsConfiguration[] configs = new CarUxRestrictionsConfiguration[] {
+        CarUxRestrictionsConfiguration[] configs = new CarUxRestrictionsConfiguration[]{
                 // Driving state with no speed range
                 new Builder()
                         .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
@@ -490,8 +564,8 @@
         assertTrue(dump.contains("Speed Range"));
         assertTrue(dump.contains("Requires DO?"));
         assertTrue(dump.contains("Restrictions"));
-        assertTrue(dump.contains("Passenger mode"));
-        assertTrue(dump.contains("Baseline mode"));
+        assertTrue(dump.contains("passenger mode"));
+        assertTrue(dump.contains("baseline mode"));
     }
 
     public void testSetUxRestrictions_UnspecifiedModeDefaultsToBaseline() {
@@ -506,7 +580,7 @@
         assertEquals(UX_RESTRICTIONS_NO_VIDEO, restrictions.getActiveRestrictions());
 
         assertTrue(restrictions.isSameRestrictions(
-                config.getUxRestrictions(DRIVING_STATE_PARKED, 0f, UX_RESTRICTIONS_BASELINE)));
+                config.getUxRestrictions(DRIVING_STATE_PARKED, 0f, UX_RESTRICTION_MODE_BASELINE)));
     }
 
     public void testSetUxRestrictions_PassengerMode() {
@@ -530,7 +604,41 @@
         assertEquals(UX_RESTRICTIONS_NO_VIDEO, baseline.getActiveRestrictions());
     }
 
-    public void testPassengerModeFallbackToBaseline() {
+    @Test
+    public void testGetUxRestrictions_WithUndefinedMode_FallbackToBaseline() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
+                .build();
+
+        CarUxRestrictions passenger = config.getUxRestrictions(
+                DRIVING_STATE_PARKED, 0f, UX_RESTRICTION_MODE_PASSENGER);
+        assertTrue(passenger.isRequiresDistractionOptimization());
+        assertEquals(UX_RESTRICTIONS_NO_VIDEO, passenger.getActiveRestrictions());
+    }
+
+    @Test
+    public void testPassengerMode_GetMovingWhenNotDefined_FallbackToBaseline() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .build();
+
+        // Retrieve with passenger mode for a moving state
+        CarUxRestrictions passenger = config.getUxRestrictions(
+                DRIVING_STATE_MOVING, 1f, UX_RESTRICTION_MODE_PASSENGER);
+        assertTrue(passenger.isRequiresDistractionOptimization());
+        assertEquals(UX_RESTRICTIONS_NO_VIDEO, passenger.getActiveRestrictions());
+    }
+
+    @Test
+    public void testPassengerMode_GetSpeedOutsideDefinedRange_FallbackToBaseline() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
                         .setDistractionOptimizationRequired(true)
@@ -605,8 +713,8 @@
                         new DrivingStateRestrictions().setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
                 .build();
 
-        assertTrue(one.equals(other));
-        assertTrue(one.hashCode() == other.hashCode());
+        assertEquals(one, other);
+        assertEquals(one.hashCode(), other.hashCode());
     }
 
     public void testConfigurationEquals_DifferentRestrictions() {
@@ -681,14 +789,15 @@
         CarUxRestrictionsConfiguration deserialized =
                 CarUxRestrictionsConfiguration.CREATOR.createFromParcel(parcel);
         assertEquals(deserialized, config);
-        assertTrue(deserialized.getPhysicalPort() == null);
+        assertNull(deserialized.getPhysicalPort());
     }
 
     /**
      * Writes input config as json, then reads a config out of json.
      * Asserts the deserialized config is the same as input.
      */
-    private void verifyConfigThroughJsonSerialization(CarUxRestrictionsConfiguration config) {
+    private void verifyConfigThroughJsonSerialization(CarUxRestrictionsConfiguration config,
+            int schemaVersion) {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out))) {
             config.writeJson(writer);
@@ -700,8 +809,8 @@
         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
         try (JsonReader reader = new JsonReader(new InputStreamReader(in))) {
             CarUxRestrictionsConfiguration deserialized = CarUxRestrictionsConfiguration.readJson(
-                    reader);
-            assertTrue(config.equals(deserialized));
+                    reader, schemaVersion);
+            assertEquals(config, deserialized);
         } catch (Exception e) {
             e.printStackTrace();
             fail();
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
index 30649e0..bb3c7bf 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
@@ -19,7 +19,6 @@
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
-import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -45,6 +44,9 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarUxRestrictionsConfigurationXmlParserTest {
+
+    private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
+
     private Context getContext() {
         return InstrumentationRegistry.getTargetContext();
     }
@@ -167,7 +169,7 @@
         Set<Byte> expected = new ArraySet<>();
         expected.add((byte) 1);
         expected.add((byte) 2);
-        for (CarUxRestrictionsConfiguration config: configs) {
+        for (CarUxRestrictionsConfiguration config : configs) {
             assertTrue(expected.contains(config.getPhysicalPort()));
         }
     }
@@ -179,7 +181,7 @@
                 CarUxRestrictionsConfigurationXmlParser.parse(
                         getContext(), R.xml.ux_restrictions_multiple_display_ports);
 
-        for (CarUxRestrictionsConfiguration config: configs) {
+        for (CarUxRestrictionsConfiguration config : configs) {
             CarUxRestrictions r = config.getUxRestrictions(DRIVING_STATE_PARKED, 0f);
             assertEquals(1, r.getMaxContentDepth());
             assertEquals(1, r.getMaxCumulativeContentItems());
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
index 0aa89f9..0b77d8e 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
@@ -15,9 +15,17 @@
  */
 package com.android.car;
 
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
+import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+
 import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION;
 import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,7 +48,6 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.util.JsonReader;
 import android.util.JsonWriter;
 
 import androidx.test.InstrumentationRegistry;
@@ -59,12 +66,12 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -72,6 +79,9 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarUxRestrictionsManagerServiceTest {
+
+    private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
+
     private CarUxRestrictionsManagerService mService;
 
     @Mock
@@ -115,7 +125,15 @@
         CarUxRestrictionsConfiguration config = createEmptyConfig();
 
         assertTrue(mService.saveUxRestrictionsConfigurationForNextBoot(Arrays.asList(config)));
-        assertTrue(readFile(staged).equals(config));
+
+        String expectedConfig = "{\"schema_version\":2,\"restrictions\":[{\"physical_port\":null,"
+                + "\"max_content_depth\":-1,\"max_cumulative_content_items\":-1,"
+                + "\"max_string_length\":-1,\"baseline\":{\"parked_restrictions\":[{\"req_opt"
+                + "\":true,\"restrictions\":511}],\"idling_restrictions\":[{\"req_opt\":true,"
+                + "\"restrictions\":511}],\"moving_restrictions\":[{\"req_opt\":true,"
+                + "\"restrictions\":511}],\"unknown_restrictions\":[{\"req_opt\":true,"
+                + "\"restrictions\":511}]}}]}";
+        assertEquals(readFile(staged.toPath()), expectedConfig);
         // Verify prod config file was not created.
         assertFalse(new File(mTempSystemCarDir, CONFIG_FILENAME_PRODUCTION).exists());
     }
@@ -162,6 +180,71 @@
     }
 
     @Test
+    public void testLoadConfig_SupportsLegacyV1() throws IOException {
+        String v1LegacyJsonFormat = "[{\"physical_port\":1,\"max_content_depth\":2,"
+                + "\"max_cumulative_content_items\":20,\"max_string_length\":21,"
+                + "\"parked_restrictions\":[{\"req_opt\":false,\"restrictions\":0}],"
+                + "\"idling_restrictions\":[{\"req_opt\":true,\"restrictions\":7}],"
+                + "\"moving_restrictions\":[{\"req_opt\":true,\"restrictions\":8}],"
+                + "\"unknown_restrictions\":[{\"req_opt\":true,\"restrictions\":511}],"
+                + "\"passenger_parked_restrictions\":[{\"req_opt\":false,\"restrictions\":0}],"
+                + "\"passenger_idling_restrictions\":[{\"req_opt\":true,\"restrictions\":56}],"
+                + "\"passenger_moving_restrictions\":[{\"req_opt\":true,\"restrictions\":57}],"
+                + "\"passenger_unknown_restrictions\":[{\"req_opt\":true,\"restrictions\":510}]}]";
+        setupMockFileFromString(CONFIG_FILENAME_PRODUCTION, v1LegacyJsonFormat);
+
+        CarUxRestrictionsConfiguration actual = mService.loadConfig().get(0);
+
+        CarUxRestrictionsConfiguration expectedConfig = new Builder()
+                .setPhysicalPort((byte) 1)
+                .setMaxContentDepth(2)
+                .setMaxCumulativeContentItems(20)
+                .setMaxStringLength(21)
+                .setUxRestrictions(DRIVING_STATE_PARKED,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(false)
+                                .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                                .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_IDLING,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(7)
+                                .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_MOVING,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(8)
+                                .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_UNKNOWN,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(511)
+                                .setMode(UX_RESTRICTION_MODE_BASELINE))
+                .setUxRestrictions(DRIVING_STATE_PARKED,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(false)
+                                .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_IDLING,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(56)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_MOVING,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(57)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_UNKNOWN,
+                        new CarUxRestrictionsConfiguration.DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(510)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .build();
+        assertTrue(actual.equals(expectedConfig));
+    }
+
+    @Test
     public void testLoadConfig_PromoteStagedFileWhenParked() throws Exception {
         CarUxRestrictionsConfiguration expected = createEmptyConfig();
         // Staged file contains actual config. Ignore prod since it should be overwritten by staged.
@@ -466,14 +549,21 @@
         return f;
     }
 
-    private CarUxRestrictionsConfiguration readFile(File file) throws Exception {
-        try (JsonReader reader = new JsonReader(
-                new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
-            CarUxRestrictionsConfiguration config = null;
-            reader.beginArray();
-            config = CarUxRestrictionsConfiguration.readJson(reader);
-            reader.endArray();
-            return config;
+    private File setupMockFileFromString(String filename, String config)
+            throws IOException {
+        File f = new File(mTempSystemCarDir, filename);
+        assertTrue(f.createNewFile());
+
+        if (config != null) {
+            try (FileOutputStream writer = new FileOutputStream(f)) {
+                byte[] bytes = config.getBytes();
+                writer.write(bytes);
+            }
         }
+        return f;
+    }
+
+    private String readFile(Path path) throws Exception {
+        return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
     }
 }